您如何在Bash中区分两个管道?


143

在不使用Bash中的临时文件的情况下,如何区分两个管道?假设您有两个命令管道:

foo | bar
baz | quux

您想diff在其输出中找到。一种解决方案显然是:

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

是否可以在不使用Bash中的临时文件的情况下这样做?您可以通过管道传递到diff之一来摆脱一个临时文件:

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

但是,您不能同时将两个管道(至少没有明显的方式)传送到diff中。是否有一些巧妙的技巧/dev/fd可以在不使用临时文件的情况下做到这一点?

Answers:


146

单行包含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或其他内容,而不是两个编号的fds,来更清楚地提醒您输入的是哪个。


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

在没有“ magical” /dev/fd或的系统上/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从标准输入读一个输入,并使用命名管道作为其他:

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

5
即使没有bash中,你可以使用临时先进先出mkfifo a; cmd >a& cmd2|diff a -; rm a
unhammer

您可以对args之一使用常规管道pipeline1 | diff -u - <(pipeline2)。然后,输出将通过显示-- /dev/stdinvs. ++ /dev/fd/67或某物而不是两个编号的fds来更清楚地提醒您哪个输入。
彼得·科德斯

进程替换(foo <( pipe ))不会修改文件系统。 管道是匿名的 ; 它在文件系统中没有名称。外壳使用pipe系统调用来创建它,而不是mkfifostrace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'如果您想自己查看,可使用它来跟踪文件和文件描述符系统调用。在Linux上,它/dev/fd/63/proc虚拟文件系统的一部分;它会自动为每个文件描述符包含条目,并且不是内容的副本。因此,除非foo 3<bar.txt计算
在内,

@PeterCordes好点。我已将您的评论包括在答案中,以提高知名度。
VonC

1
@PeterCordes我将对您进行任何编辑:这就是让Stack Overflow变得有趣的原因:任何人都可以“修复”答案。
VonC

127

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

例如:

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

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


1
比我对相同解决方案(匿名批处理)的修订要详细得多。+1
VonC

4
这在Bash中称为进程替换
富兰克林·于

5

到达此页面的某些人可能正在寻找逐行比较,comm或者grep -f应该使用逐行比较。

要指出的一件事是,在所有答案的示例中,差异只有在两个流都完成后才真正开始。使用以下方法进行测试:

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

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

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

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

但是不同之处在于马上seq 100就会有所不同seq 10。请注意,如果流之一是a tail -f,则差异不能通过进程替换来完成。

这是我写的关于在终端上扩散流的博客文章,其中介绍了sd

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.