12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

簡単に使えるエレガントなオプション解析ライブラリ(シェルスクリプト用)

Last updated at Posted at 2020-08-12

続編 → シェルスクリプト(bash等)の引数解析が究極的に簡単になりました

以前書いた「高機能で短いシェルスクリプト用のオプション解析コード(POSIXシェル準拠・独自実装)」を元に機能強化しライブラリにしたオプションパーサー「getoptions」を作成しました。宣言的なパーサー定義関数を書くだけでオプション解析が行なえます。ループをぐるぐる回してオプションをチェックしていくコードはもう必要ありません。getopt → getopts → gnugetopt → getoptions と名前が長くなるほど高機能になっていると言えば覚えやすいと思います。(gnu-getopt は無理やりですが 笑)

bash 依存なしなので dash を含む全ての POSIX シェルに対応しており、外部コマンドもほぼ使ってない(cat のみ、オプション機能であるヘルプの自動生成で使用)ので軽快に動作します。オプションの指定方法はロングオプションを含む一般的によく使われているもの(例 -abc, -vvv, -ovalue, --long, --option=value, -- 等)であれば対応しています。(おそらく POSIX および GNU のオプションの仕様を満たせてるのではないかと。)またエラーメッセージを置き換えたりバリデーションやヘルプの自動生成機能もあります。2020-11-14追記 ロングオプション名を省略して指定できる機能と、サブコマンドへの対応を追加しました。

細かい使い方は README.md 参照ですが、以下の例から直感的にわかるのではないかと思います。

#!/bin/sh

VERSION=1.0

. ./getoptions.sh # もしくは中身を本体のスクリプトに貼り付け

# オプションパーサーの定義関数
parser_definition() {
  setup   RESTARGS plus:true error:error \
      -- "Usage: ${2##*/} [options] [arguments...]" '' 'getoptions sample' ''
  msg -- 'Options:'
  flag    FLAG_A  -a                                   -- "message a"
  flag    FLAG_B  -b                                   -- "message b"
  flag    FLAG_F  -f +f --{no-}flag                    -- "expands to --flag and --no-flag"
  flag    VERBOSE -v    --verbose counter:true init:=0 -- "e.g. -vvv is verbose level 3"
  param   PARAM   -p    --param                        -- "accepts --param value / --param=value"
  param   NUMBER  -n    --number  validate:number      -- "accepts only a number value"
  option  OPTION  -o    --option  default:"default"    -- "accepts -ovalue / --option=value"
  disp    :usage  -h    --help
  disp    VERSION       --version
}

number() { case $OPTARG in (*[!0-9]*) return 1; esac; }
error() {
  case $2 in
    number) echo "option '$1' is not a number" ;;
    *) return 1 ;; # その他はデフォルトのエラーを表示
  esac
}

# parser_definition 関数を元に オプションパーサーである parse 関数を定義
eval "$(getoptions parser_definition parse "$0")"
# parser_definition 関数を元に ヘルプ表示用の usage 関数を定義(省略可)
eval "$(getoptions_help parser_definition usage "$0")"

parse "$@"               # オプション解析
eval "set -- $RESTARGS"  # オプション以外の引数を取得するために $@ を再設定

echo "$FLAG_A"
printf '%s\n' "$@" # オプション以外の残りの引数

上記の例は以下のようなオプションに対応させるためのコードです。

./sample/basic.sh -ab -f +f --flag --no-flag -vvv -p value -ovalue --option=value 1 2 -- 3 -f

細かい話

getoptions 関数は正確にはオプションパーサーのジェネレータです。getoptions 関数で生成したコードを eval することでオプションパーサー関数(上記の例では parse 関数)を定義します。eval せずに getoptions 関数単体で実行するとオプションパーサーのコードが出力されます。この生成されたコードのみを使用することも可能で、この場合は getoptions.sh は不要となります。

getoptions.sh がグローバルに定義する関数は getoptions 関数と getoptions_help 関数のみです。その他の関数(setup 関数や flag 関数等)はコマンド置換($(...))のサブシェル内に閉じ込められるためグローバルを汚しません。また オプションパーサーで使用する変数は OPTARGOPTIND のみです。この2つの変数は本来 getopts シェルビルトインコマンドで使われる変数ですが getoptions を使用するなら getopts は不要となるはずなので再利用しています。

getoptions_help 関数も同様に usage 関数を定義します。このヘルプの自動生成機能はオプション機能です。使い捨てのスクリプト程度であれば十分だと思いますが、出力が気に入らない場合はその他の方法で出力するなり改造するなりしてください。getoptions_help 関数は getoptions 関数と完全に独立しているので不要な場合は問題なく削除できます。(余談ですがヘルプの見やすさにこだわりたい場合は docopts のような仕組みを追加したほうが良いと思います。)

おまけ: ksh93u のバグ

ksh93u で以下のコードがエラーになりハマった・・・(ksh93u 以前及び ksh2020 では問題なし)

#!/bin/sh
set -eu 1 2
VAR='var'
while [ $# -gt 0 ]; do
  [ $# -eq 1 ] && unset VAR # 最後のループで unset する
  if [ "${VAR+x}" ]; then   # VAR 変数が定義(set)されていたら?
    echo "$VAR"             # 定義されてないのに実行される => エラー「VAR: parameter not set」
  fi
  shift
done

[ "${VAR+x}" ]VAR 変数が定義されているときだけ echo で変数を参照するはずなのですが unset しても定義されていると誤判定します。もちろん実際には定義されてないためエラーになります。ワークアラウンドは eval '[ "${VAR+x}" ]' とすれば OK です。eval することで内部の何かがリセットされるのでしょう。

さいごに

似たようなツールはすでにあるのではないかと作る前にリサーチしましたが残念ながら見つかりませんでした。見つかったとしても bash 専用だったりしますし。bash 専用に目をつぶったとしても使い方や機能に満足がいくものはありませんでした。

ライセンスは「Creative Commons Zero v1.0 Universal」にしており(可能な限り)権利を放棄しているので、ライブラリとして使用したりスクリプト内にコピーしたり不要な部分を削除したり改造したりとライセンスのことを一切気にせず自由に使うことができます。

ということで私も自作ツールのオプション解析処理をこれに置き換えていこうと思います。

12
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?