echo vs <<<,还是Bash Award中无用的echo?


19

到目前为止cat无用使用是众所周知的,并且还提到了无用的使用echo(与该问题无关)。我想知道是否应该有“无用的echoBash奖”:根据一些非常不科学的测量,管道似乎比heredocs和herestring慢得多:

  • Heredocs:

    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中标准输入的命令的一般规则?


4
我一定会允许您“无用的seq”奖励;-)
克里斯·

2
请注意,您应该比较cat <<ENDvs. cat <<< "test string"vs. echo "test string" | catcat <<'END'vs. cat <<< 'test string'vs. echo 'test string' | cat,以使Shell对字符串执行相同数量的扩展。
manatwork 2012年

@ChrisDown Derp。我总是忘记那个。谢谢!
l0b0 2012年

@manatwork感谢,固定和更新的时间。
l0b0 2012年

只是出于好奇,那代表是什么?您的初衷不是要循环$ reps次$(seq $reps)吗?
manatwork 2012年

Answers:


11

首先,让我们专注于性能。我在运行Debian压缩的本来主要是闲置的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没有herestrings)。

这是三倍的中位数:

$ 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并致电破折号或什至更好的ksh。Bash的功能无法弥补其相对缓慢,但ksh具有功能和速度。

除了性能之外,还有清晰度和可移植性。<<<是ksh93 / bash / zsh扩展名,它不如echo … |或知名<<。它不适用于ksh88 / pdksh或POSIX sh。

唯一<<<可以说明显更清晰的地方是heredoc:

foo=$(tr a-z A-Z <<<'hello world')

foo=$(tr a-z A-Z <<'EOF'
hello world
EOF
)

(大多数shell无法在包含的行的结尾处关闭括号<<EOF。)


2
请注意,<<<它来自rc,最初是由引入带Bourne的外壳世界的zshrc没有添加尾随换行符,但所有的bashzshksh93做AFAICT。rc使用管有,而bashzshksh93使用像here文档的临时文件。
斯特凡Chazelas

@StephaneChazelas糟糕,我应该检查一下,谢谢。这使得<<<有用性更低。
吉尔斯(Gilles)'所以

还要注意,zsh进行了优化,=(<<<foo)可以有效地创建一个包含“ foo”的临时文件,该文件不涉及派生进程=(echo foo)
斯特凡Chazelas

我还要提到的pdksh也没有包括<<<
kurtm 2013年

@kurtm“在ksh88 / pdksh或POSIX sh中均不起作用。”
别再作恶了

6

使用heredocs的另一个原因(如果您没有足够的话)是,如果不使用流,回显可能会失败。考虑使用bash pipefail选项:

set -o pipefail
foo=yawn
echo $foo | /bin/true ; echo $?  # returns 0

/bin/true不会消耗其标准输入,但echo yawn仍会完成。但是,如果要求回显打印大量数据,则直到完成后它才会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)(之所以添加128是因为bash根据bash(1)这样做:

当命令在致命信号N上终止时,bash将使用128 + N的值作为退出状态。

Heredocs没有这个问题:

/bin/true <<< $foo$foo$foo$foo ; echo $?  # returns 0 always

谢谢,这个特殊的细微差别导致我的脚本在随机位置间歇性地失败,在Amazon服务器上比在kvm主机上更多,但是它仍然不时失败,在看似不可能发生的地方。pipefail默认情况下处于关闭状态是一件好事!
mogsie 2013年

2
哦,我pipefail在每个脚本的顶部都启用了。给我所有的错误!
2013年

@ l0b0嗯。也许我会将pipefail添加到j.mp/safebash中,我全都在开发过程中遇到了所有错误!
布鲁诺·布罗诺斯基

2

您可能要使用echo的原因之一是为了扩展对添加到heredocs和herestring末尾的换行符的控制:

三个字符的foo长度为3:

$ echo -n foo | wc -c
3

但是,三个字符的此处字符串是四个字符:

$ wc -c <<< foo
4

三个字符的heredoc也是如此:

$ wc -c << EOF
foo
EOF
4

第四个字符是换行符0x0a

这在某种程度上与bash从子shell抓取输出时删除这些换行符的方式神奇地契合:

这是一个返回四个字符的命令:foo\n。“ \ n”通过回显添加,除非您指定以下-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语句更改为read

if [ $(echo -n "$foo" | wc -c) -eq $(wc -c < something.txt) ] ; then

然后测试通过。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.