使用进程替换和使用管道有何区别?

9

我在 tee 信息页面中发现了一个关于使用 tee 实用程序的示例:

wget -O - http://example.com/dvd.iso | tee >(sha1sum > dvd.sha1) > dvd.iso

我查找了>(...)语法并发现了一种称为“进程替换”的东西。据我所知,它使一个进程看起来像是另一个进程可以将其输出写入/附加到的文件。(如果我在这一点上错了,请纠正我。)
那么这与管道(|)有什么不同呢?我看到上面的例子中使用了管道--这只是一个优先级问题吗?还是有其他的区别?
2个回答

12

这里没有任何好处,因为这行代码同样可以写成这样:

wget -O - http://example.com/dvd.iso | tee dvd.iso | sha1sum > dvd.sha1

当你需要对多个程序进行输入输出管道操作时,就会出现差异,因为这些操作不能仅通过|符号来表达。可以尝试:

# Calculate 2+ checksums while also writing the file
wget -O - http://example.com/dvd.iso | tee >(sha1sum > dvd.sha1) >(md5sum > dvd.md5) > dvd.iso

# Accept input from two 'sort' processes at the same time
comm -12 <(sort file1) <(sort file2)

在某些情况下,当您无法或不想使用管道时,它们也非常有用:

# Start logging all error messages to file as well as disk
# Pipes don't work because bash doesn't support it in this context
exec 2> >(tee log.txt)
ls doesntexist

# Sum a column of numbers
# Pipes don't work because they create a subshell
sum=0
while IFS= read -r num; do (( sum+=num )); done < <(curl http://example.com/list.txt)
echo "$sum"

# apt-get something with a generated config file
# Pipes don't work because we want stdin available for user input
apt-get install -c <(sed -e "s/%USER%/$USER/g" template.conf) mysql-server

所以,这实际上就像是说> filename.ext< filename.ext,但是它是一个进程而不是一个文件? - Ashton Wiersdorf
5
是的。echo <(date)cat <(date) 给出了这个如何工作的良好线索。 - that other guy
7
与重定向有一个重要的区别:它并不重定向任何内容,而是将管道的名称(实际上类似于"/dev/fd/63")作为参数传递给命令,并期望该命令打开该“文件”并进行读/写操作。@thatotherguy 给出的 echo <(date) 示例应该可以说明这一点,因为它只是打印文件名而不是从文件中读取内容。 - Gordon Davisson

2
另一个主要区别是返回值/退出代码的传播(我将使用更简单的命令来说明):
管道:
$ ls -l /notthere | tee listing.txt
ls: cannot access '/notthere': No such file or directory
$ echo $?
0

-> tee的退出码被传播

进程替换:

$ ls -l /notthere > >(tee listing.txt)
ls: cannot access '/notthere': No such file or directory
$ echo $?
2

-> ls的退出代码被传递

当然,有几种方法可以解决这个问题(例如set -o pipefail、变量PIPESTATUS),但我认为值得一提的是,这是默认行为。


另一个相当微妙但可能令人烦恼的区别在于子进程终止(最好使用产生大量输出的命令进行说明):

管道:

#!/usr/bin/env bash
tar --create --file /tmp/etc-backup.tar --verbose --directory /etc . 2>&1 | tee /tmp/etc-backup.log
retval=${PIPESTATUS[0]}
(( ${retval} == 0 )) && echo -e "\n*** SUCCESS ***\n" || echo -e "\n*** FAILURE (EXIT CODE: ${retval}) ***\n"

-> 在包含管道结构的行之后,所有管道命令已经终止(否则PIPESTATUS无法包含它们各自的退出代码)

进程替换:

#!/usr/bin/env bash
tar --create --file /tmp/etc-backup.tar --verbose --directory /etc . &> >(tee /tmp/etc-backup.log)
retval=$?
(( ${retval} == 0 )) && echo -e "\n*** SUCCESS ***\n" || echo -e "\n*** FAILURE (EXIT CODE: ${retval}) ***\n"

-> 在进程替换所在的行之后,即在>(...)内部的命令,在本例中是tee可能仍在运行,可能会导致不同步的控制台输出(成功/失败消息与仍在流动的tar输出混合)。[*]


[*] 可以在帧缓冲控制台上重现,但似乎不会影响像KDE的Konsole这样的GUI终端(可能是由于不同的缓冲策略)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接