在bash中,通常使用流程替换或管道更好

时间:2018-01-28 09:47:35

标签: bash

在使用单一命令的输出仅由另一个命令使用的用例中,最好使用|pipelines)或<()({{3 }})?

当然,更好是主观的。对于我的具体用例,我是以性能为主要驱动因素,但也对稳健性感兴趣。

while read do done < <(cmd)我已经了解并已切换到的好处。

我有几个var=$(cmd1|cmd2)个实例我怀疑可以更好地替换为var=$(cmd2 < <(cmd1))

我想知道后一种情况对前者带来的具体好处。

1 个答案:

答案 0 :(得分:3)

tl; dr:使用烟斗,除非你有令人信服的理由不这样做。

从进程替换中管道和重定向stdin基本上是一回事:两者都将导致由匿名管道连接的两个进程。

有三个实际差异:

1。 Bash默认为管道中的每个阶段创建一个fork。

这就是为什么你开始研究这个问题的原因:

#!/bin/bash
cat "$1" | while IFS= read -r last; do true; done
echo "Last line of $1 is $last"

默认情况下,此脚本不会使用管道工作,因为与kshzsh不同,bash将为每个阶段分叉子shell。

如果您在bash 4.2+中设置shopt -s lastpipe,则bash会模仿kshzsh行为,并且运行正常。

2。 Bash不会等待进程替换完成。

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等待headtr完成,因此看起来似乎更慢。

在procsub版本中,bash只等待head,并让tr在后​​台完成。

3。 Bash目前不会针对流程替换中的单个简单命令优化离开。

如果调用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的规范方式并且在所有平台上都能正常工作并不会造成损害。