如何区分 Bash 中的两个管道?

如果不使用 Bash 中的临时文件,如何使用 差异两个管道? 假设您有两个命令管道:

foo | bar
baz | quux

你想在他们的输出中找到 diff,一个解决方案显然是:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

有没有可能在不使用 Bash 中的临时文件的情况下这样做?您可以通过管道连接到其中一个管道来删除一个临时文件:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

但是您不能同时将两个管道导入 diff (至少不能以任何明显的方式)。在不使用临时文件的情况下,是否存在一些涉及 /dev/fd的巧妙技巧?

37370 次浏览

一行包含2个 tmp 文件(不是您想要的)将是:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

对于 Bash,你可以试试:

 diff <(foo | bar) <(baz | quux)


foo | bar | diff - <(baz | quux)  # or only use process substitution once

第二个版本将更清楚地提醒您哪个输入是哪个,通过显示
-- /dev/stdin++ /dev/fd/63之类的,而不是两个编号的 fds。


甚至连命名管道都不会出现在文件系统中,至少在那些 bash 可以实现进程替换的操作系统中是这样,bash 可以使用诸如 /dev/fd/63之类的文件名来获得一个文件名,命令可以从这个文件名中打开和读取,以便从 bash 在执行命令之前设置的已经打开的文件描述符中实际读取。(例如,bash 使用 fork 之前的 pipe(2),然后在 fd 63上使用 dup2quux的输出重定向到 diff的输入文件描述符。)

在没有“神奇的”/dev/fd/proc/self/fd的系统上,bash 可能会使用命名管道来实现进程替换,但是它至少会自己管理它们,这与临时文件不同,而且您的数据不会被写入文件系统。

您可以检查 bash 如何使用 echo <(true)实现进程替换来打印文件名,而不是从中读取文件名。它在一个典型的 Linux 系统上打印 /dev/fd/63。或者更详细地了解系统调用 bash 所使用的内容,Linux 系统上的这个命令将跟踪文件和文件描述符系统调用

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

如果没有 bash,您可以创建一个命名管道 。使用 -告诉 diff读取来自 STDIN 的一个输入,并使用命名管道作为另一个输入:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

请注意,只能使用 tee 命令将 一个输出通过管道传送到 多重输入:

ls *.txt | tee /dev/tty txtlist.txt

上面的命令将 ls * . txt 的输出显示到终端,并将其输出到文本文件 txtlist.txt。

但是通过进程替换,您可以使用 tee将相同的数据提供给多个管道:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar

在 bash 中,您可以使用子 shell,通过将管道放在括号中来单独执行命令管道。然后,可以在这些管道前面加上 < ,以创建匿名命名管道,然后传递给 diff。

例如:

diff <(foo | bar) <(baz | quux)

匿名命名管道是由 bash 管理的,因此它们是自动创建和销毁的(与临时文件不同)。

一些到达这个页面的人可能正在寻找一个逐行差异,对于这个差异,应该使用 commgrep -f

需要指出的一点是,在所有答案的示例中,直到两个流都完成时,差异才会真正开始。用以下方法进行测试:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

如果这是一个问题,您可以尝试 SD(流 diff) ,它不需要排序(像 comm那样) ,也不需要像上面的示例那样进行进程替换,它比 grep -f快几个数量级,并且支持无限流。

我提议的测试示例在 sd中是这样编写的:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

但不同的是,seq 100将不同于 seq 10马上。注意,如果其中一个流是 tail -f,则不能使用进程替换来完成 diff。

下面是我写的关于终端上不同流的 博客文章,它介绍了 sd