コマンドのパスを知りたいんじゃなく、コマンドの存在をチェックしたいだけならwhich
よりhash
を使ったほうが良いかもっていう話。→追記: type
が最強っぽい。
追記: command -v
も良い。プログラムの存在チェックorパスを探したいだけなら互換性を考えると一番良いかも。
比較してみる
whichよりhashよりtype=command -vが高速→typeまたはcommand -vの勝ち
whichは実ファイルという実体があるプログラムです。hashとtypeはbashの組み込みコマンドです。なので当然ですがプログラムの起動コストがない分hashやtypeの方が速いです。
$ time bash -c 'for((i=0;i<10000;i++));do which perl; done >/dev/null'
real 0m7.739s
user 0m2.928s
sys 0m5.923s
$ time bash -c 'for((i=0;i<10000;i++));do hash perl; done >/dev/null'
real 0m0.633s
user 0m0.240s
sys 0m0.391s
$ time bash -c 'for((i=0;i<10000;i++));do type perl; done >/dev/null'
real 0m0.603s
user 0m0.168s
sys 0m0.434s
$ time bash -c 'for((i=0;i<10000;i++));do command -v perl; done >/dev/null'
real 0m0.602s
user 0m0.172s
sys 0m0.400s
1万回回してますがhashやtypeはwhichの10倍以上速い。そして僅差ですがtypeの方がhashより速いっぽいです、これは何回やっても同じくらい速い。ってことでtypeの勝ち。command -vも計測してみたところ何回やってもほぼtypeと同じ結果でした。
探せるものの違い→typeの勝ち
コマンドとして使えそうなものとしては、プログラム・組み込みコマンド・エイリアス・関数・あとはifなどの予約後があり、whichとhashとtypeでは探せるものに違いがあります。
例 | which | hash | type | |
---|---|---|---|---|
プログラム | /usr/bin/perl | ○ | ○ | ○ |
組み込みコマンド | cd, alias, read, hash, ... | ○ | ○ | |
エイリアス | alias ll='ls -l' | ○ | ○ | |
関数 | f(){ echo func; }, nvm | ○ | ○ | |
予約語 | if, for, ... | ○ |
とりあえずtype最強のようです。
nvmとかのように関数として定義されるコマンドをチェックしたい場合は基本、whichでは探せません。
補足(whichで関数を探す)
一応whichでも、非常にアクロバティックな書き方ですが関数定義があるかどうかのチェックができなくはないです。
$ f() { echo func; }
$ declare -f | command which --read-functions f
f ()
{
echo func
}
$ echo $?
0
declare -f で関数定義をダンプします。その関数定義のダンプを標準入力から読み込んだ上で --read-functions オプションをつけると、その読み込んだ関数定義内に引数で渡された関数が存在するかのチェックができます。これはman whichに書いてありますが、さすがにこんなの普通は使わないと思うwww
##環境非依存度→hash,typeの勝ち
Bashスクリプトを書こうとしている以上は組み込みコマンドであるhash
とtype
は常に存在を保証されています。それに対してwhich
は外部プログラムであるがゆえにインストールされていないこともあり環境非依存度という点では劣ります。(まぁ、組み込みとかでない限り大抵のディストリビューションでは普通入ってますけどね)
というわけで、hashとtypeの勝ち。
キャッシュの影響による正確さ→whichの勝ち?
hashとtypeは組み込みコマンドだけあって毎回ゼロから起動されるwhichよりも賢いです。
実はbashは、初めてperlとかを実行した際にPATHを前から順にチェックして /usr/bin/perlがあることを発見したらperl=/usr/bin/perlということをメモリ内に学習して、2度目以降perlを実行する際にはPATHのチェックせずにいきなり/usr/bin/perlを実行します。そしてhash
とtype
はまずこのbash内部のハッシュテーブルをチェックして、エントリが無ければPATH環境変数を元にコマンドを探し始めます。組み込みコマンドな上にメモリキャッシュも活用してるからwhichより早いのは当然ですね。
ただしあらゆるキャッシュにつきもののトラブルですが、この学習が邪魔になることもあります。
# 前準備
mkdir /tmp/test
cp /bin/cat /tmp/test/copycat
export PATH="/tmp/test:$PATH"
# これは当然どっちも問題なし
which copycat # 見つかる→正解
hash copycat # 見つかる→正解
type copycat # 見つかる→正解
# さっきまであったコマンドを消してみる
rm -f /tmp/test/copycat
which copycat # 見つからない→正解
hash copycat # 見つかる→不正解
type copycat # 見つかる→不正解
# type -aだとハッシュを見なくなる
type -a copycat # 見つからない→正解
# ハッシュをクリアすると再度PATHをチェックする
hash -r
hash copycat # 見つからない→正解
type copycat # 見つからない→正解
というわけでコマンドの存在チェックとしての正確さでは、毎回律儀にチェックする(むしろそれしか出来ない)which
に軍配が上がりそう?それに対してhashとtypeはハッシュクリア対応可能、またtypeは追加オプションでも対応可能ってことで優劣はつけがたいかも。
複数コマンドを纏めて探す→引き分け
which perl python ruby
やhash perl python ruby
やtype perl python ruby
やcommand -v perl python ruby
という感じで、複数コマンドのありかを纏めてチェックするのはwhichでもhashでもtypeでもcommand -vでも出来ます。どちらもこの場合、全てが見つかったら成功で一つでも見つからなかったら失敗という判定。
hashは標準出力が出ないのでちょっとだけ記述が楽→hashの勝ち
そもそも別の用途のコマンドなんで仕様上当然なんですが以下の様な出力の違いがあります。
$ which perl; echo $?
/usr/bin/perl
0
$ which aaa; echo $?
/usr/bin/which: no aaa in (/usr/local/bin:/bin:/usr/bin)
1
$ hash perl; echo $?
0
$ hash aaa; echo $?
-bash: hash: aaa: not found
1
$ type perl; echo $?
0
$ type aaa; echo $?
-bash: type: aaa: not found
1
なので、if文で使うときは邪魔な標準出力やエラー出力を隠すために↓こうなります。
if which python >/dev/null 2>&1; then
# some code...
fi
if hash python 2>/dev/null; then
# some code...
fi
if type python >/dev/null 2>&1; then
# some code...
fi
2>&1
が不要になった分、>/dev/null
が2>/dev/null
になってますがトータルでhashの方が3文字少ないのでhashの勝ち。
まとめ
こんな感じに、思いついた争点を列挙してみたわけで結果は以下のとおり。
which | hash | type | |
---|---|---|---|
速度 | × | ○ | ◎ |
探せるもの | △ | ○ | ◎ |
環境非依存度 | △ | ○ | ○ |
正確さ | ○ | △ | △ |
タイプ数 | △ | ○ | △ |
複数同時チェック | 引き分け | 引き分け | 引き分け |
異論あるかもしれんが、総じて→異論あった結果hash
の勝ちtype
最強っぽいです!