在使用单一命令的输出仅由另一个命令使用的用例中,最好使用|
(pipelines)或<()
({{3 }})?
当然,更好是主观的。对于我的具体用例,我是以性能为主要驱动因素,但也对稳健性感兴趣。
while read do done < <(cmd)
我已经了解并已切换到的好处。
我有几个var=$(cmd1|cmd2)
个实例我怀疑可以更好地替换为var=$(cmd2 < <(cmd1))
。
我想知道后一种情况对前者带来的具体好处。
答案 0 :(得分:3)
tl; dr:使用烟斗,除非你有令人信服的理由不这样做。
从进程替换中管道和重定向stdin基本上是一回事:两者都将导致由匿名管道连接的两个进程。
有三个实际差异:
这就是为什么你开始研究这个问题的原因:
#!/bin/bash
cat "$1" | while IFS= read -r last; do true; done
echo "Last line of $1 is $last"
默认情况下,此脚本不会使用管道工作,因为与ksh
和zsh
不同,bash
将为每个阶段分叉子shell。
如果您在bash 4.2+中设置shopt -s lastpipe
,则bash会模仿ksh
和zsh
行为,并且运行正常。
POSIX只需要一个shell来等待管道中的最后一个进程,但是包括bash
在内的大多数shell都会等待所有这些进程。
当你有一个生产缓慢的生产者时,这会产生显着的差异,就像/dev/random
密码生成器一样:
tr -cd 'a-zA-Z0-9' < /dev/random | head -c 10 # Slow?
head -c 10 < <(tr -cd 'a-zA-Z0-9' < /dev/random) # Fast?
第一个例子不会有利地进行基准测试。在head
满足并退出后,tr
将等待下一次write()
调用,以发现管道已损坏。
由于bash等待head
和tr
完成,因此看起来似乎更慢。
在procsub版本中,bash只等待head
,并让tr
在后台完成。
如果调用sleep 1
之类的外部命令,那么Unix进程模型需要bash forks并执行命令。
由于叉子很贵,bash可以优化它的情况。例如,命令:
bash -c 'sleep 1'
天真地会产生两个叉子:一个用于运行bash,另一个用于运行sleep
。但是,bash可以对其进行优化,因为在bash
完成后sleep
不需要留下来,所以它可以用sleep
替换自己(execve
没有fork
)。这与尾调用优化非常相似。
( sleep 1 )
同样已经过优化,但<( sleep 1 )
不是。 source code没有提供特殊原因,因此可能没有出现。
$ strace -f bash -c '/bin/true | /bin/true' 2>&1 | grep -c clone
2
$ strace -f bash -c '/bin/true < <(/bin/true)' 2>&1 | grep -c clone
3
鉴于上述情况,您可以创建一个支持您想要的任何位置的基准,但由于叉的数量通常更相关,因此管道将是最佳默认值。
显然,管道是POSIX标准,连接两个进程的stdin / stdout的规范方式并且在所有平台上都能正常工作并不会造成损害。