How to check if command option is enabled in POSIX shell script
シェルスクリプトで,コマンドのオプションが有効かどうかの判定方法を記す。
Introduction
複数のマシンを使っていると,マシンによってコマンドオプションが異なることがある。これは,主に以下2点の理由が原因だ。
- コマンドのバージョンの違い
- 開発元の違い
1のバージョンによるオプションの違いの例としては,GNU grepコマンドがある。GNU grepでは,version 2.5.2から--exclude-dir
オプションが追加された。このオプションを使うことで,検索対象外ディレクトリを設定できる。しかし,version 2.5.2以前の古いマシンのGNU grepではこのオプションは存在しない。
2の開発元の違いの例としては,lsコマンドがある。lsコマンドの表示結果に色を付けるオプションが存在している。このオプション名がLinuxたと--color
だが,Macだと-G
である。
これらのコマンドのオプションの有無による問題は,POSIXで未定義のベンダーの独自拡張オプションを使っていることが原因だ。これらのオプションをそもそも使わなければいいという考え方もある。しかし,利便性を考えるならこれらのオプションが使えるなら使いたい。例えば,grepの--exclude-dir
オプションで,.gitや.svnなど常に検索対象外にしたいファイルを指定して予めaliasで指定しておいたがほうが便利だ。lsの表示結果に色をつけるオプションについても同様だ。
POSIX原理主義的に考えるならば,オプションが存在するときだけ使うようにきちんどガードすれば問題ないだろう。オプションが存在しなくてもエラーを出さずに処理は通常どおり行い,可用性を維持すればいい。
Method
オプションの存在の有無の判定方法には2通りの方法がある。
- コマンドのバージョンから判定
- コマンドのオプションの存在の判定
1. のコマンドのバージョンから判定する方法では,--version
オプションにより表示されるバージョン番号からオプションが存在するかどうかを判断する。この方法では,以下2点の欠点があるので不利だ。
- オプションがサポートされるバージョン番号の把握が必要
- 開発元の違いによる判定が煩雑
2.の方法では,--help
オプションにより表示されるオプション一覧からオプションの有無を判断する。この方法では,実際にオプションが存在するかどうかを判定するので確実だ。
なお,--version
オプションと--help
オプションは,GNU Coding Standardsで規定されており,存在する可能性が高い。
参考:4.7 Standards for Command Line Interfaces – GNU Coding Standards
しかし,--help
と--version
オプションはPOSIXで規定されていないので全コマンドに存在するとは限らない。そのため,そのままではPOSIX原理主義に反してしまう。そこで,これらのオプションが存在しないことも考慮して,2>&1
により標準エラー出力も含めて判定条件として扱う。これにより,POSIXの範囲内の動作だけでオプション有無の判定ができる。
Coding
それでは,実際にオプションの判定方法を説明しよう。
まず,コマンドのオプションとして想定されるパターンを考える。そのためのコマンドのオプションの挙動としてPOSIXの以下の文書を参照する。
参考:12 Utility Conventions – The Open Group Base Specifications Issue 7, 2016 Edition
上記からオプション有無の判定で必要な項目と,それらの項目が実際に--help
でどのように表示されるかの例を以下の表にまとめた。
項目 | –helpでの表示例 |
---|---|
ショートオプション | -m |
ロングオプション | –merge |
引数なしオプション | -m |
必須引数ありオプション | -N, –first-line-number=NUMBER |
任意引数ありオプション | -S[STRING], –sep-string[=STRING] |
任意引数ありオプションは,POSIXでは非推奨とされており,実装されているコマンドは少ない。pr
コマンドはこれらの全てのオプションが実装されているPOSIX準拠コマンドなのでとても参考になる。
--help
で表示される文字列に対して,POSIXで定義されているgrepコマンドでのマッチを用いて,オプションの有無を判定する。オプションの有無を判定する構文は以下となる。
以下で上記の構文の内容を解説する。
- コマンドのヘルプを
<command> --help 2>&1 |
により,--help
が存在しない場合のエラーも含めてパイプで渡す。 - コマンドが成功したかどうかの終了ステータスでオプションの有無を判定するので,grepの
-q
オプションにより,マッチしたときの余計な情報を表示させない。 - 引数
--
により,マッチに使用するオプションをgrepのオプションではなく引数として扱う。 - GNU prの
pr --help
の表示例より,オプションの直後に続く文字は", =["
の4文字に限られることがわかる。後ろの4文字を付けなければ,他のオプションの部分文字列としてマッチする可能性がある。また,Busyboxなどでは空白の代わりにタブが使われている。これらをオプションの直後に置いて’<option>[[:blank:],=[]'
によりマッチングさせる。[:blank:]
は空白とタブにマッチする。
毎回上記の構文を記述するのは少し長ったらしいので,関数にしてしまおう。
is_option_enabled
関数の第1引数にコマンド,第2引数にオプションを指定して実行する。
なお,関数の最後が"$OPT"'[[:blank:],=[]'
のように,$OPT
とそれ以降を別の引用符で囲んでいる。"$OPT[=[, ]"
としてもbashで動くのだが,zshでinvalid subscriptというエラーが表示されてしまうのでこちらを採用した。これは,zshでは"$OPT[]"
のブロックで配列と誤認されてしまうからのようだ。
Conclusion
POSIX原理主義でコマンドに特定のオプションが存在するかの判定方法について説明した。コマンドの有無に比べたらマイナーで,あまり使う場面がないかもしれない。しかし,alias
の設定においては重要だと思う。実際に,grepの--exclude-dir
オプションは常に指定したいので,今回説明した方法でif文でガードをかけてからalias
でgrepを再定義している。
自分のPOSIX原理主義を実践していく上で必要な情報は今後もまとめていきたい。