148

如何在不使用 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可以在不使用临时文件的情况下做到这一点?

4

3 回答 3

156

包含 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/stdinvs.++ /dev/fd/63或其他内容,而不是两个编号的 fd。


文件系统中甚至不会出现命名管道,至少在 bash 可以通过使用文件名来实现进程替换的操作系统上,例如/dev/fd/63获取命令可以打开和读取的文件名,以实际从 bash 设置的已打开文件描述符中读取在执行命令之前启动。(即 bashpipe(2)在 fork 之前使用,然后在 fd 63 上dup2从 的输出重定向quux到 的输入文件描述符diff。)

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

您可以检查 bash 如何实现进程替换echo <(true)以打印文件名而不是从中读取文件名。/dev/fd/63它在典型的 Linux 系统上打印。或者有关 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
于 2008-12-05T23:40:58.060 回答
132

在 bash 中,您可以使用子shell 来单独执行命令管道,方法是将管道括在括号中。然后,您可以在它们前面加上 < 以创建匿名命名管道,然后您可以将其传递给 diff。

例如:

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

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

于 2008-12-05T23:49:51.397 回答
7

到达此页面的一些人可能正在寻找逐行差异,comm或者grep -f应该使用它来代替。

需要指出的一件事是,在所有答案的示例中,差异实际上不会在两个流完成之前开始。用例如测试这个:

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

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

我建议的测试示例将这样写sd

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

但不同的是,这seq 100将立即有所不同seq 10。请注意,如果其中一个流是 a tail -f,则无法通过进程替换来完成差异。

这是我写的一篇关于在终端上区分流的博文,其中介绍了sd.

于 2016-08-01T08:40:27.280 回答