bash では basename や dirname を使うより ${0##*/} や ${0%/*} を使ったほうが速い?

「わたし、気になります!」的な話。

今やってる作業の途中で、install_name_tool を使ってライブラリのリンクパスを一括変換するのに以下のようなコマンドを書いたことがあった。

while read f
do
    install_name_tool -id @executable_path/lib/$(basename $f) $f
done < hoge.txt

install_name_tool -id の場合はいいけど、install_name_tool -change の場合はライブラリが多いと2000行くらいになるのでかなり時間がかかる。install_name_tool が重いのかと思い、コマンドを外部ファイルに書き出して実行してみるとそれほど時間はかからない。ということは原因は basename の方っぽい。


んで、以下のようなスクリプトを書いてみた。${} 内の ## というのは この記事 でもちょっと書いたけど、変数内の文字列を先頭から最長一致で取り除く bash の機能。つまり、##*/ と書けば先頭から最後のスラッシュまでを取り除く。

#!/bin/bash

time echo ${0##*/}
time basename $0

で、実行したみた。

test.sh

real	0m0.001s
user	0m0.000s
sys	0m0.000s
test.sh

real	0m0.003s
user	0m0.001s
sys	0m0.002s

変数内文字列置換(?)の方が速い。まぁ、普通に考えれば basename の方は $0 を一度展開してから basename コマンドを呼び出すから時間がかかるのは当たり前か。


今度は dirname で。変数内文字列置換の方は % で後方から最短一致で最初のスラッシュまでを削除する。

#!/bin/bash

echo '${0%/*}'
time for ((i=0;i<=1000;i++))
do
	echo ${0%/*} > /dev/null
done

echo 'dirname $0'
time for ((i=0;i<=1000;i++))
do
	dirname $0 > /dev/null
done

で、実行したみた。

${0%/*}

real	0m0.103s
user	0m0.070s
sys	0m0.029s
dirname $0

real	0m2.759s
user	0m0.551s
sys	0m1.960s

さすがに1000回も処理があると違いがはっきりわかる。


今度から bash 環境では dirnamebasename はあまり使わないようにしよう。


最近、zsh を使い始めたけど、こっちは変数とか引数に :h とか :t とか付けるだけでもできる。超ラクチン。bash にも :h とかはあるけど記法があるけど、履歴参照時しか使えないっぽい?なんかいい方法ないかな。

Trackback