f

アーカイブ

2016-10-23

How to check if command option is enabled in POSIX shell script

シェルスクリプトで,コマンドのオプションが有効かどうかの判定方法を記す。

Introduction

複数のマシンを使っていると,マシンによってコマンドオプションが異なることがある。これは,主に以下2点の理由が原因だ。

  1. コマンドのバージョンの違い
  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. コマンドのバージョンから判定
  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でどのように表示されるかの例を以下の表にまとめた。

コマンドオプションの種類とpr --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 | grep -q -- '<option>[[:blank:],=[]'

以下で上記の構文の内容を解説する。

  1. コマンドのヘルプを<command> --help 2>&1 |により,--helpが存在しない場合のエラーも含めてパイプで渡す。
  2. コマンドが成功したかどうかの終了ステータスでオプションの有無を判定するので,grepの-qオプションにより,マッチしたときの余計な情報を表示させない。
  3. 引数--により,マッチに使用するオプションをgrepのオプションではなく引数として扱う。
  4. GNU prのpr --helpの表示例より,オプションの直後に続く文字は", =["の4文字に限られることがわかる。後ろの4文字を付けなければ,他のオプションの部分文字列としてマッチする可能性がある。また,Busyboxなどでは空白の代わりにタブが使われている。これらをオプションの直後に置いて'<option>[[:blank:],=[]'によりマッチングさせる。[:blank:]は空白とタブにマッチする。

毎回上記の構文を記述するのは少し長ったらしいので,関数にしてしまおう。

コマンドオプションの有無を判定するis_option_enabled関数
#!/bin/sh
# \file      is_option_enabled.sh
# \author    SENOO, Ken
# \copyright CC0

set -u

is_option_enabled()(
 CMD="$1"; OPT="$2"
 $CMD --help 2>&1 | grep -q -- "$OPT"'[[:blank:],=[]'
)

is_option_enable "$@"

## Test
# is_option_enabled ls   --test        && echo "OK" || echo "NG"  # NG
# is_option_enabled grep --exclude-dir && echo "OK" || echo "NG"  # OK

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原理主義を実践していく上で必要な情報は今後もまとめていきたい。

0 件のコメント:

コメントを投稿