サブコマンドは今時のトレンド
ここでサブコマンドと呼んでいるのは、yum
や git
のように、第1引数にさらにコマンドのような文字列が続くものです。
このようなコマンドは apt
系などのパッケージコマンドや cvs
以降のバージョン管理システムなどでもおなじみでしたが、git
の登場によって一気に一般的になった気がします。
書籍「UNIXという考え方」 ではコマンドは単機能であるべきということが書かれていますが、昨今はそこそこ複雑な機能を持ったコマンドが多く、この原則は通用しなくなってきているように思えます。
もっとも、サブコマンドが今までのコマンドに相当する部分で、サブコマンドを持つコマンドは名前空間であったり Facade パターンであったりといった解釈をしたほうがいいかもしれません。
最近でも aws
コマンドなどもサブコマンドの模範例となっている感じです。
対して一番厄介なのが rpm
コマンドのようなオプション体系で、rpm -qi
といった問い合わせ情報 (query information) オプションは -q
に -i
が従属しているということから -iq
と書くと別の意味になるというあれ。これは POSIX もビックリな(禁止はしていないとは思うものの)仕様じゃないでしょうか。イディオムで書くオプション以外はいつも考えこんでしまいます。
サブコマンドの作成例
ここではシェルスクリプトとして Bash を書くことにします。可搬性もバッチリなので。
Bash には case
文があるので、これを元にディスパッチ的なことをやると一番見通しがいいなと今は感じています。
#!/bin/bash
subcommand="$1"
shift
case $subcommand in
foo)
echo "foo"
;;
bar)
echo "bar"
;;
*)
echo "default"
;;
esac
case
文の中に多数の文を書くと見通しが悪くなるので、そこはスクリプト内で関数にまとめてしまうとよいでしょう。
#!/bin/bash
function foo {
echo "foo"
}
function bar {
echo "bar"
}
subcommand="$1"
shift
case $subcommand in
foo)
foo
;;
bar)
bar
;;
*)
echo "default"
;;
esac
Bash には Perl のようなコンパイル時と実行時の区別は無いといっていいので、function はコマンド行に置かれる前に定義されている必要があります。
名前を制限してコマンドインジェクション的なことが起こらないことを保証すれば、渡されたコマンド自体を実行するようなディスパッチスタイルも可能でしょう。
#!/bin/bash
function dispatch-foo {
echo "foo"
}
function dispatch-bar {
echo "bar"
}
subcommand="dispatch-$1"
shift
if type "$subcommand" >/dev/null 2>&1 ; then
$subcommand "$@"
else
echo "dispatch failed"
fi