実行環境によって利用するコマンドを選択する(シェルスクリプト)

課題

ウェブコンテンツをダウンロードするツールとして wget コマンドや curl コマンドが代表的だが、スクリプト実行環境でどちらが利用可能かあらかじめわからない。 このようなときはどのようにスクリプトを記述すれば良いか。

解決策

実行環境で利用できるコマンドをスクリプトの先頭で確認し、利用できるコマンドを変数として保存しておく。

以下のリポジトリスクリプトで実際にこの手法を用いている。 https://github.com/altarterminal/hobbytool

この問題が発生する原因は wget コマンドや curl コマンドが「標準的ではない」ことだ。 もっと具体的に言うと、wget コマンドや curl コマンドは POSIX に規定されていないからだ(※現実では POSIX に規定されていれば必ず安全というわけでもないのだが...)。 例えば Ubuntu を利用しているなら、wget を使うためのパッケージや curl を使うためのパッケージをインストールすることになるだろう。 どのコマンドを利用するかはその環境の作成者に依存してしまう。

それならば、どのコマンドを利用していてもスクリプトが動くように、スクリプト内で利用可能なコマンドを確認しよう。 コマンドの利用可否を確認するためには type コマンドを利用できる。 利用可能であれば type コマンドはゼロで終了し、利用不可能であればゼロ以外で終了する。 これをもって条件分岐を行うのだ。

if   type curl >/dev/null 2>&1; then
  wcmd="curl -sS -f -L -o"
elif type wget >/dev/null 2>&1; then
  wcmd="wget -q -O"
else
  echo "${0##*/}: wget or curl is needed" 1>&2
  exit 11
fi

上記のスクリプトではまず curl コマンドの存在を確認し、次に wget コマンドの存在を確認している。 どちらも存在しない場合はエラーメッセージを出力してスクリプトをエラー終了するようにしている。 curl コマンドまたは wget コマンドが存在する場合は、それを呼び出すためのコマンドラインを wcmd 変数に保存する。 コマンドごとに必要なオプションは異なるので、どちらが選択されても動作が同じになるようにそれぞれで適切なオプションを設定する。

上記の変数は以下のように評価してコマンドを呼び出している。

...
$wcmd "コンテンツ保存先ファイル名" "コンテンツURL"
...

ここで「$wcmd」をクォートしていないことに注意したい。 シングルクォーテーションは言わずもがな、ダブルクォーテーションもつけてはならない。 クォートをしてしまうと、シェルによる単語分割がスキップされてしまい、「curl」や「wget」の単語がそれとして認識されないからだ。

他の方法として、eval コマンドを利用してシェルの評価を2段階で行うようにしても良い。

所感

この記事に記載している内容は POSIX 原理主義における交換可能性に関するものである。 標準的ではない機能に関して、単一の実装に依存しないような記述とすることで、移植性を高めている。 実行環境に存在するか不安なコマンドを利用している場合は、スクリプトの先頭で確認を行い、利用が不可能ならばその場でエラー終了すると安全だ。