シェルスクリプトでgit
とかみたいなサブコマンドを持ったコマンドツールを作る。
サブコマンドを使えるコマンド
git
などのコマンドはgit <subcommand> [options]
的な感じで
サブコマンドによってgit
のどの様な操作をするか、を指定しますが、
このサブコマンドを自作することも簡単にできます。
自作コマンドはPATH
の通ったディレクトリにgit-<command>
のように
git-
から始まるコマンドを置けばgit <command>
のように使えるようになります。
これみたいなことをシェルスクリプトでやってみます。
mycmd
というコマンドを作って、mycmd-<command>
というスクリプトなどをmycmd <command>
の形で実行できるようにします。
こんな感じのスクリプト。
PREFIXはコマンドフィアルの名前にしています。
もしこれがシンボリックリンクなどで別の名前に貼られるとそっちがPREFIXとして使われるので注意。
(もしそれが想定されてないなら中でPREFIX="mycmd-"
のようにしてしまう。)
外部コマンドと中で使う関数に関してmycmd-
と付くものを使えるようにしてあります。
関数は.bashrcなどで定義してログインシェル上で定義されていてもスクリプトの中には引き継がれないので使えません。
使うのであればスクリプトの中でsource .bashrc
とかする必要があります。
スクリプトの中で
- 関数: mycmd-help, mycmd-commands, mycmd-test
が設定されています。別途、PATH
の通っているディレクトリに
mycmd-script
1
2
3
4
5
| #!/usr/bin/env bash
echo mycmd-script
for arg in "$@";do
echo "arg: $arg"
done
|
といったスクリプトを実行権限を与えて置いておきます。
これで使ってみると
1
2
3
| $ mycmd
Usage: mycmd <sub-commands>
Sub commands: commands help test script
|
こんな感じで引数なしだとhelp
が呼ばれるようになっているのでこのような表示で、
定義しているコマンドが全て見えています。
あとは各種コマンドも
1
2
3
4
5
6
7
8
9
10
| $ mycmd test a "b c" d
This is mycmd-test
arg: a
arg: b c
arg: d
$ mycmd script a "b c" d
mycmd-script
arg: a
arg: b c
arg: d
|
`
このように使え、引数もきちんと渡されています。
get_commands
という関数が少し長いですが、
これは全ての定義されたものを探すだけの関数なので、もし渡されたコマンドがなければエラーを返すだけで良いなら
この関数は除いても良くて、
最小版だと以下のような感じ。
mycmd
1
2
3
4
5
6
7
8
9
10
11
12
13
| #!/usr/bin/env bash
PREFIX="$(basename "$0"}-"
cmd="$1"
shift
if ! type -t "$PREFIX$cmd" >&/dev/null;then
echo "Unknown command: $cmd"
exit 1
fi
"$PREFIX$cmd" "$@"
|
これで適当な名前のファイルに保存して置いて、後はサブコマンドをPATHの下に置いていけば
自由にサブコマンド付きのコマンドが作れます。
上では関数とPATH下の外部実行ファイルのみを探していますが、
もし、aliasも使いたい場合は
aliasは通常はインタラクティブなシェルでのみ有効でスクリプト中だと定義しても無視されてしまうため、
1
| shopt -s expand_aliases
|
のオプション設定を最初に実行する必要があります。
また、get_commands
の中で、関数に加えて、
1
2
3
4
5
6
| # get functions
IFS=' ' read -ra array <<< "$(declare -F |grep "declare -f $PREFIX"|cut -d' ' -f3 | tr $'\n' ' ')"
cmd_cand=("${cmd_cand[@]}" "${array[@]}")
# get aliases
IFS=' ' read -ra array <<< "$(alias |grep "^alias $PREFIX"|cut -d' ' -f2|cut -d= -f1 | tr $'\n' ' ')"
cmd_cand=("${cmd_cand[@]}" "${array[@]}")
|
のようにaliasも探すようにします。
最後の実行部分でも、このままだとalias
は見つけられないので、eval
を使い、
1
| eval "$PREFIX$cmd" "$@"
|
のように書き直す必要があります。
ただし、これだと引数の囲いが意味なくなるので、上のmycmd test a "b c" d
の結果とかは
1
2
3
4
5
6
| $ mycmd test a "b c" d
This is mycmd-test
arg: a
arg: b
arg: c
arg: d
|
のように引数の中にスペースがあるとそれも分けて見てしまいます。
他にもエスケープ文字とかが入ってたりするとちょっと動作が変わることもあるので、
どうしても必要でない限りはaliasは無理に使う必要はないかと思います。
(ほぼほぼ関数で代用できるので。)