LoginSignup
9
8

More than 5 years have passed since last update.

備忘録: 最小限の労力でサブコマンド付きのシェルスクリプトが書きたい

Last updated at Posted at 2017-09-18

なるべく労力をかけずにサブコマンド付きのシェルスクリプトを書きたい、ということがあった。

労力をかけないことに力点をおいたので、 POSIX 準拠ではなく、 Bash 限定の要素が入っている。

コマンドをそのまま実行するとヘルプ表示

$ some-command
Usage:
  some-command --help
  some-command sub-command1
  some-command sub-command2

--help オプションでもヘルプを表示

$ some-command --help
Usage:
  some-command --help
  some-command sub-command1
  some-command sub-command2

sub-command1, sub-command2 でそれぞれ違った処理を実行する。

※ 下記の例示はただの echo

$ some-command sub-command1
1
$ some-command sub-command2
2

存在しないサブコマンドにもヘルプ表示

また、エラー扱いとして exit code 1 で終了したい。

$ some-command wrong-command
Usage:
  some-command --help
  some-command sub-command1
  some-command sub-command2
$ echo $?
1

どう書く

サブコマンドをシェルスクリプトの関数として対応関係にあれば、そこそこ可読性が高く、と追加や制御の手間が最小限になると考えた。

some-command
#!/usr/bin/env bash
sub-command1() {
  echo 1
}

sub-command2() {
  echo 2
}

--help() {
  echo 'Usage:'
  compgen -A function \
    | xargs -I % echo ' ' "$(basename "$0")" %
}

main() {
  unset -f -- "${FUNCNAME[0]}"
  ! declare -F -- "$1" >/dev/null && --help && exit 1
  "$@"
}

main "$@"

テクニック解説

テクニック1. main をサブコマンドから消す

unset -f -- "${FUNCNAME[0]}"

${FUNCNAME[0]} で自身の関数名が取得できるので、 unset すると名前空間から main が消えてくれる。後々の compgen -A function でサブコマンドを得るときに main がない方が良い。

テクニック 2. 簡易ヘルプ

本来は入力補間を実現するのための組み込みコマンドとして compgen というものがある。これをヘルプの表示につかう。具体的には、関数を列挙してサブコマンドとして表示する。

大本の実行コマンドは $(basename "$0") から得る。

compgen -A function \
    | xargs -I % echo ' ' "$(basename "$0")" %

テクニック 3. 関数名の存在チェック

declare -F を使って関数名の存在チェックをしている。なければヘルプ表示

  ! declare -F -- "$1" >/dev/null && --help && exit 1

テクニック 4. "$@"

シェル関数を先頭で消費しつつ、残りを引数として関数に渡せる。
なので例えばもし sub-command1

sub-command1() {
  echo "$@"
}

のような定義にすれば、

$ some-command sub-command1 A B C
A B C

となる。
サブコマンドで、実際の処理は他のコマンドに委譲する場合に便利になる。

まとめ

--help から 最後の行まではボイラープレートとして捉えて、コピペすると楽でしょう。

ここはコピペ
# http://qiita.com/kitsuyui/items/4b204963e0ebec53fe3c

--help() {
  echo 'Usage:'
  compgen -A function \
    | xargs -I % echo ' ' "$(basename "$0")" %
}

main() {
  unset -f -- "${FUNCNAME[0]}"
  ! declare -F -- "$1" >/dev/null && --help && exit 1
  "$@"
}

main "$@"
9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8