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

Ada*_*eld 138 bash diff pipeline

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

foo | bar
baz | quux
Run Code Online (Sandbox Code Playgroud)

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

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b
Run Code Online (Sandbox Code Playgroud)

是否可以在Bash中不使用临时文件的情况下这样做?您可以通过在其中一个管道中管道来消除一个临时文件:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -
Run Code Online (Sandbox Code Playgroud)

但是你不能同时将两个管道同时传输到diff中(至少不是以任何明显的方式).是否有一些聪明的技巧涉及/dev/fd不使用临时文件这样做?

Von*_*onC 138

带有2个tmp文件的单行(不是你想要的)将是:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt
Run Code Online (Sandbox Code Playgroud)

使用bash,您可以尝试:

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

 foo | bar | diff - <(baz | quux)  # or only use process substitution once
Run Code Online (Sandbox Code Playgroud)

第二个版本将更清楚地提醒您哪个输入是通过显示
-- /dev/stdinvs. ++ /dev/fd/63或什么,而不是两个编号的fds.


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

在一个系统中没有"神奇" /dev/fd或者/proc/self/fd,庆典可能会使用命名管道实现进程替换,但它至少会管理它们本身,不像临时文件,和你的数据不会被写入到文件系统.

您可以检查bash如何实现进程替换echo <(true)以打印文件名而不是从中读取.它打印/dev/fd/63在典型的Linux系统上.或者有关bash使用的系统调用的更多详细信息,Linux系统上的此命令将跟踪文件和文件描述符系统调用

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

没有bash,你可以创建一个命名管道.使用-告诉diff从标准输入读一个输入,并使用命名管道作为其他:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt
Run Code Online (Sandbox Code Playgroud)

请注意,您只能使用tee命令将一个输出传递给多个输入:

ls *.txt | tee /dev/tty txtlist.txt 
Run Code Online (Sandbox Code Playgroud)

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

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

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
Run Code Online (Sandbox Code Playgroud)

  • 即使没有bash,你也可以使用临时的fifo的`mkfifo a; cmd> a&cmd2 | diff a - ; rm a` (5认同)

Ben*_*enM 122

在bash中,您可以使用子shell,通过将管道括在括号内来单独执行命令管道.然后,您可以使用<来创建匿名命名管道,然后将其传递给diff.

例如:

diff <(foo | bar) <(baz | quux)
Run Code Online (Sandbox Code Playgroud)

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

  • 这在Bash中称为[进程替换](https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html). (4认同)

mlg*_*mlg 5

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

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

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)
Run Code Online (Sandbox Code Playgroud)

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

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

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'
Run Code Online (Sandbox Code Playgroud)

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

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