Bashシェルでtypeコマンドを使ってコマンドの存在チェックを行う際の注意
はじめに
昔から、Linux 上で任意のコマンドがどこにあるかを確認するために which
コマンドを使っていました。
最近では type
コマンドを使ってコマンドがインストールされているかを確認することが増えてきました。
ところがつい最近、注意しないといけないことに気がつきました。
検証環境
$ uname -moi
aarch64 aarch64 GNU/Linux
$ head -n 3 /etc/os-release
PRETTY_NAME="Ubuntu Kinetic Kudu (development branch)"
NAME="Ubuntu"
VERSION_ID="22.10"
$ bash -version | head -n 1
GNU bash, バージョン 5.2.0(1)-release (aarch64-unknown-linux-gnu)
type
コマンドとは
type
を使うと、引数の文字列に一致するコマンドの情報を表示できます。
ここで「コマンド」と呼んでいるものは以下のようなものになります。
- 実行可能ファイル ( シェル、Python、Ruby、Perl などのスクリプトやコードからコンパイルされた実行可能なバイナリファイル )
- 予約語 ( keyword )
- ビルトインコマンド ( builtin )
- エイリアス ( alias )
- 関数 ( function )
以下の例は rm
コマンドに関する情報を表示しています。
$ type rm
rm は /usr/bin/rm です
「実行可能ファイル」だけは、 $PATH
環境変数に設定されているパスのリスト ( Linux 環境では :
が区切り文字として使われます) から検索されます。
# /usr/local/sbin ディレクトリが一番最初に検索される
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# `rm` というスクリプトを /usr/local/bin に作成してみる
$ echo 'echo hello' > /usr/local/bin/rm
$ chmod 777 /usr/local/bin/rm
$ type rm
rm は /usr/bin/rm です
type
コマンドの終了ステータス
type
コマンド実行後の終了ステータスは以下のようになります。
- 検索対象の「コマンド」が見つかった場合 →
0
- 検索対象の「コマンド」が見つからなかった場合 →
1
# git コマンドを検索
$ type git
git は /usr/bin/git です
$ echo $?
0
# svn コマンドを検索
$ type svn
bash: type: svn: 見つかりません
$ echo $?
1
これを利用してコマンドが存在していた場合 ( あるいは存在していなかった場合 ) のみ何らかの処理を行う、といったことが可能です。
type
コマンドが出力する内容を無視するために、 >/dev/null 2>&1
を末尾に追加しています。
# 見つかった場合、メッセージが表示される
$ type git >/dev/null 2>&1 && echo "見つかったよ"
見つかったよ
# 見つからなかった場合、メッセージが表示されない
$ type svn >/dev/null 2>&1 && echo "見つかったよ"
「実行可能ファイル」だけを検索対象とする場合は -P
オプションを使う
今回紹介したかったのはここからです。
例えば git
がインストールされているかどうかを確認したいとしましょう。
先の例で注意しないといけない点があります。
svn
という予約語やビルトインコマンドは(おそらく)ありませんが、エイリアスや関数が存在していた場合を実験してみます。
# エイリアスを設定
$ alias svn='echo hello'
$ type svn >/dev/null 2>&1 && echo "見つかったよ"
見つかったよ
# お掃除
$ unalias svn
# 関数を設定
$ function svn () { echo world; }
$ type svn >/dev/null 2>&1 && echo "見つかったよ"
見つかったよ
# お掃除
$ unset svn
実行可能ファイルは存在しませんが、エイリアスや関数に反応してコマンドの存在チェックが成功してしまいます。
これを回避するために -P
オプションを使用します。-P
オプションを付与したときは、実行可能ファイルが見つかったときはフルパスが出力されますが、見つからなかったときは何も出力されないため 2>&1
の記述は不要です。
# エイリアスを設定
$ alias svn='echo hello'
# 何も表示されない
$ type -P svn >/dev/null && echo "見つかったよ"
# お掃除
$ unalias svn
# 関数を設定
$ function svn () { echo world; }
# 何も表示されない
$ type -P svn >/dev/null && echo "見つかったよ"
# お掃除
$ unset svn
実際に実行可能ファイルが存在していたときにも正しく動作するか試してみましょう。
$ type -P git >/dev/null && echo "見つかったよ"
見つかったよ
ちなみに、当然ですが type -P
実行後に実行可能ファイルが見つかったときの終了ステータスは 0
、見つからなかった場合の終了ステータスは 1
となります。
# 見つかったファイルのフルパスが出力される
$ type -P git
/usr/bin/git
$ echo $?
0
$ type -P svn
$ echo $?
1
"type -P" で実行可能ファイルが予約語、エイリアス、関数と重複していた場合はどうなる?
実行可能ファイルが予約語、エイリアス、関数と重複していた場合は type -P
コマンドの実行結果はどうなるでしょう。
ここでは time
コマンドを例に試してみます。time
コマンドは Bash の予約語でありながら、 /usr/bin/time
に存在する実行可能ファイルでもあります。
エイリアスと関数を用意すれば、準備完了です。
# type を実行すると予約語が優先して見つかる
$ type time
time はシェルの予約語です
# which を実行すると、実行可能ファイルが見つかる
$ which time
/usr/bin/time
# エイリアス設定
$ alias time="echo hello"
# 関数設定
$ function time () { echo world; }
type -P
コマンドを実行してみます。
$ type -P time
/usr/bin/time
めでたく「実行可能ファイル」のみが検索対象として見つかりました。type -P
とwhich
の実行結果は同じと考えて良さそうです。
ここから更に調査を進めてみました。
実行可能ファイル、予約語、エイリアス、関数がすべて存在した場合 type
はどんな挙動をするの?
せっかく time
コマンドについて実行可能ファイル、予約語、エイリアス、関数をすべて用意したのです。
このような状況下で type
はどんな挙動をするのかを調べてみました。
type
コマンドには -a
というオプションがあります。-a
オプションを付けるとすべてのコマンドを検索し結果を表示してくれます。
試してみます。
$ type -a time
time は `echo hello' のエイリアスです
time はシェルの予約語です
time は関数です
function time ()
{
echo world
}
time は /bin/time です
time
コマンドの検索結果がすべて出力されました。
出力内容から予想が付きそうですが、 type
を実行した際に利用されるコマンドの優先順位は以下のようになりそうです。
- エイリアス
- 予約語
- 関数
- 実行可能ファイル
type
コマンドをオプション指定なしで実行してみます。
$ type time
time は `echo hello' のエイリアスです
予想通り、エイリアスが見つかります。
今度はエイリアスを削除してから time
コマンドを検索してみます。
$ unalias time
$ type time
time はシェルの予約語です
予想通りですね。
予約語を消すことはできないので、今回はここまでにしておきました。
ひとこと
特定のツールが使える状態にあるかどうかを判定する場合は、 エイリアスや関数を誤検知しないように以下のように判定するのが良いでしょう。
if ! type -P one_tool >/dev/null; then
echo "one_tool is not exist."
fi
ディスカッション
コメント一覧
まだ、コメントがありません