サブコマンドを持つコマンドを作るシェルスクリプトのスタイル

  • 10
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

サブコマンドは今時のトレンド

ここでサブコマンドと呼んでいるのは、yumgit のように、第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