到现在为止cat
奖励无用是众所周知的,并且还提到了无用的使用echo
(与这个问题无关)。我想知道是否应该有一个“ echo
Bash 中无用使用奖”:根据一些非常不科学的测量,管道似乎比heredocs和herestrings慢得多:
继承文档:
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
一般来说,heredocs 和 Herestrings 的速度大致相同(这只是多个测试中的一个数据集),而重定向始终慢 50% 以上。我是否误解了什么,或者这可以用作在 Bash 中读取标准输入的命令的一般规则吗?
答案1
首先,让我们关注性能。我在运行 Debian scrape 的 x86_64 处理器上运行了一个稍微不同的程序的基准测试,该处理器原本大部分闲置。
herestring.bash
,使用herestring传递一行输入:
#! /bin/bash
i=0
while [ $i -lt $1 ]; do
tr a-z A-Z <<<'hello world'
i=$((i+1))
done >/dev/null
heredoc.bash
,使用heredoc传递一行输入:
#! /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 和 dash 下对脚本进行了计时(除了herestring.bash
,因为 dash 没有这里字符串)。
以下是三倍的中位数:
$ 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
结论:
- Heredoc 比 Herestring 更快。
echo
管道的速度明显加快,但并没有显着加快。 (请记住,这是一个玩具程序:在真实的程序中,大部分处理时间都在tr
此处的调用所代表的内容中。)- 如果您想要速度,请放弃 bash 并调用 dash 甚至更好的 ksh 来代替。 Bash 的功能并不能弥补它的相对缓慢,但 ksh 既有功能又有速度。
除了性能之外,还有清晰度和便携性。<<<
是 ksh93/bash/zsh 扩展,但不如echo … |
或知名<<
。它在 ksh88/pdksh 或 POSIX sh 中不起作用。
唯一可以说更清晰的地方<<<
是在定界符内部:
foo=$(tr a-z A-Z <<<'hello world')
对比
foo=$(tr a-z A-Z <<'EOF'
hello world
EOF
)
(大多数 shell 无法处理包含 的行末尾的括号<<EOF
。)
答案2
使用此处文档的另一个原因(如果您没有足够的文档)是,如果未消耗流,则 echo 可能会失败。考虑使用 bashpipefail
选项:
set -o pipefail
foo=yawn
echo $foo | /bin/true ; echo $? # returns 0
/bin/true
不消耗其标准输入,但echo yawn
仍然完成。但是,如果要求 echo 打印大量数据,则只有 aftertrue
完成后才会完成:
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) (添加 128 是因为 bash 根据 bash(1) 这样做:
当命令因致命信号 N 而终止时,bash 使用 128+N 的值作为退出状态。
Heredocs没有这个问题:
/bin/true <<< $foo$foo$foo$foo ; echo $? # returns 0 always
答案3
您可能想要使用 echo 的原因之一是对添加到heredocs和herestrings末尾的换行符进行一些控制:
三个字符的foo
长度为 3:
$ echo -n foo | wc -c
3
但是,这里的三个字符的字符串是四个字符:
$ wc -c <<< foo
4
也是一个三字符的定界符:
$ wc -c << EOF
foo
EOF
4
第四个字符是换行符0x0a
。
不知何故,这神奇地符合 bash 在从子 shell 获取输出时删除这些换行符的方式:
这是一个返回四个字符的命令:foo
和\n
。 '\n' 由 echo 添加,除非您指定选项,否则它总是添加换行符-n
:
$ echo foo
foo
$ echo foo | wc -c
4
但是,通过将其分配给变量,echo 添加的尾随换行符将被删除:
$ foo=$(echo foo)
$ echo "$foo" # here, echo adds a newline too.
foo
因此,如果您混合文件和变量并在计算中使用它们(例如,您不能使用heredocs或herestrings,因为它们会添加换行符。
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
然后测试通过。