今では cat
アワードの無用の使用 は非常によく知られており、 echo
の無用の使用 (ないこの質問に関連します)。 「Bash Awardでのecho
の無用な使用」があるべきかどうか疑問に思っています。いくつかの非常に非科学的な測定によると、パイピングは、ヒアドキュメントやヘレストリングよりもはるかに遅いようです。
ヒアドキュメント:
for reps in 1 2 3
do
time for i in {1..1000}
do
cat <<'END'
test string
END
done > /dev/null
done
real 0m1.786s
user 0m0.212s
sys 0m0.332s
real 0m1.817s
user 0m0.232s
sys 0m0.332s
real 0m1.846s
user 0m0.256s
sys 0m0.320s
ヘレストリングス
for reps in 1 2 3
do
time for i in {1..1000}
do
cat <<< 'test string'
done > /dev/null
done
real 0m1.932s
user 0m0.280s
sys 0m0.288s
real 0m1.956s
user 0m0.248s
sys 0m0.348s
real 0m1.968s
user 0m0.268s
sys 0m0.324s
リダイレクション
for reps in 1 2 3
do
time for i in {1..1000}
do
echo 'test string' | cat
done > /dev/null
done
real 0m3.562s
user 0m0.416s
sys 0m0.548s
real 0m3.924s
user 0m0.384s
sys 0m0.604s
real 0m3.343s
user 0m0.400s
sys 0m0.552s
一般に、ヒアドキュメントとヒアストリングはほぼ同じ速度です(これはいくつかのテストからの1つのデータセットにすぎません)一方で、リダイレクトは常に50%以上遅くなります。私は何かを誤解していますか、またはこれはBashの標準入力を読み取るコマンドの一般的なルールとして使用できますか?
まず、パフォーマンスに集中しましょう。私は、Debian squeezeを実行している他はほとんどアイドルのx86_64プロセッサで、少し異なるプログラムのベンチマークを実行しました。
herestring.bash
、ヘレストリングを使用して入力行を渡します。
#! /bin/bash
i=0
while [ $i -lt $1 ]; do
tr a-z A-Z <<<'hello world'
i=$((i+1))
done >/dev/null
heredoc.bash
、ヒアドキュメントを使用して入力行を渡す:
#! /bin/bash
i=0
while [ $i -lt $1 ]; do
tr a-z A-Z <<'EOF'
hello world
EOF
i=$((i+1))
done >/dev/null
echo.bash
、echo
とパイプを使用して入力行を渡す:
#! /bin/bash
i=0
while [ $i -lt $1 ]; do
echo 'hello world' | tr a-z A-Z
i=$((i+1))
done >/dev/null
比較のために、ATT ksh93とダッシュの下にもスクリプトの時間を計測しました(herestring.bash
を除きます。ダッシュにはヒア文字列がないためです)。
3回の中央値は次のとおりです。
$ time bash ./herestring.bash 10000
./herestring.bash 10000 0.32s user 0.79s system 15% cpu 7.088 total
$ time ksh ./herestring.bash 10000
ksh ./herestring.bash 10000 0.54s user 0.41s system 17% cpu 5.277 total
$ time bash ./heredoc.bash 10000
./heredoc.bash 10000 0.35s user 0.75s system 17% cpu 6.406 total
$ time ksh ./heredoc.bash 10000
ksh ./heredoc.sh 10000 0.54s user 0.44s system 19% cpu 4.925 total
$ time sh ./heredoc.bash 10000
./heredoc.sh 10000 0.08s user 0.58s system 12% cpu 5.313 total
$ time bash ./echo.bash 10000
./echo.bash 10000 0.36s user 1.40s system 20% cpu 8.641 total
$ time ksh ./echo.bash 10000
ksh ./echo.sh 10000 0.47s user 1.51s system 28% cpu 6.918 total
$ time sh ./echo.sh 10000
./echo.sh 10000 0.07s user 1.00s system 16% cpu 6.463 total
結論:
echo
とパイプは顕著ですが、劇的に速くはありません。 (これはおもちゃのプログラムであることを覚えておいてください。実際のプログラムでは、ほとんどの処理時間は、ここでのtr
呼び出しの意味が何であってもかまいません。)パフォーマンスの他に、明確さと移植性もあります。 <<<
は、echo … |
または<<
よりもあまり知られていないksh93/bash/zsh拡張です。 ksh88/pdkshまたはPOSIX shでは機能しません。
<<<
が間違いなく非常に明確である唯一の場所は、ヒアドキュメント内です。
foo=$(tr a-z A-Z <<<'hello world')
対
foo=$(tr a-z A-Z <<'EOF'
hello world
EOF
)
(ほとんどのシェルは、<<EOF
を含む行の終わりにある括弧を閉じることに対応できません。)
ヒアドキュメントを使用するもう1つの理由(十分な数がない場合)は、ストリームが消費されないとエコーが失敗する可能性があるためです。 bash 'pipefail
オプションの使用を検討してください:
set -o pipefail
foo=yawn
echo $foo | /bin/true ; echo $? # returns 0
/bin/true
は標準入力を消費しませんが、echo yawn
それでも完了します。ただし、echoが大量のデータを印刷するように要求された場合、true
が完了するまで完了しません。
foo=$(cat /etc/passwd)
# foo now has a fair amount of data
echo $foo | /bin/true ; echo $? # returns 0 sometimes 141
echo $foo$foo$foo$foo | /bin/true ; echo $? # returns mostly 141
141はSIGPIPE(128 + 13)です(bashはbash(1)に従ってそうするため、128が追加されます:
コマンドが致命的な信号Nで終了すると、bashは終了ステータスとして128 + Nの値を使用します。
ヒアドキュメントにはこの問題はありません。
/bin/true <<< $foo$foo$foo$foo ; echo $? # returns 0 always
エコーを使用する理由の1つは、ヒアドキュメントとヒア文字列の最後に追加される改行文字を制御するためです。
3つの文字foo
の長さは3です:
$ echo -n foo | wc -c
3
ただし、3文字のherestringは4文字です。
$ wc -c <<< foo
4
3文字のヒアドキュメントも:
$ wc -c << EOF
foo
EOF
4
4番目の文字は改行0x0a
文字です。
どういうわけかこれは、サブシェルから出力を取得するときにbashがこれらの改行文字を削除する方法と魔法のように一致します。
4つの文字を返すコマンドは次のとおりです:foo
および\n
。 '\ n'はエコーによって追加され、-n
オプションを指定しない限り、常に改行文字が追加されます。
$ echo foo
foo
$ echo foo | wc -c
4
ただし、これを変数に割り当てると、エコーによって追加された末尾の改行が削除されます。
$ foo=$(echo foo)
$ echo "$foo" # here, echo adds a newline too.
foo
したがって、ファイルと変数を混在させて計算で使用する場合(たとえば、ヒアドキュメントまたはヒア文字列は、改行が追加されるため使用できません)。
foo=abc
echo -n 'abc' > something.txt
if [ $(wc -c <<< "$foo") -eq $(wc -c < something.txt) ] ; then
echo "yeah, they're the same!"
else
echo "foo and bar have different lengths (except, maybe not)"
fi
Ifステートメントを読み取るように変更した場合
if [ $(echo -n "$foo" | wc -c) -eq $(wc -c < something.txt) ] ; then
その後、テストに合格します。