rcmdnk's blog

Sub Command

シェルスクリプトで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は無理に使う必要はないかと思います。 (ほぼほぼ関数で代用できるので。)

Sponsored Links
Sponsored Links

« python-template: Pythonプロジェクト用GitHubレポジトリテンプレート Pythonで重いライブラリを必要な時だけimportしたいが型付けがちょっと大変になる »

}