it-swarm.cn

工艺替代和管道

我想知道如何理解以下内容:

将命令的标准输出插入另一个命令的标准输入是一项强大的技术。但是,如果您需要传递多个命令的标准输出怎么办?这是进程替代的地方。

换句话说,过程替换可以执行管道的任何操作吗?

进程替换可以做什么,而管道不能做什么?

89
Tim

解决它们之间差异的一个好方法是在命令行上做一些试验。尽管使用<字符,它的功能与重定向或管道非常不同。

让我们使用date命令进行测试。

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

这是一个毫无意义的示例,但是它表明cat接受了STDIN上date的输出并将其吐出。通过流程替换可以达到相同的结果:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

但是,幕后发生的事情是不同的。实际没有传递cat到打开并读取文件的名称,而是给它提供了STDIN流。您可以使用echo而不是cat查看此步骤。

$ echo <(date)
/proc/self/fd/11

Cat收到文件名时,它将为我们读取文件的内容。另一方面,echo只是向我们显示了已通过的文件名。如果添加更多替换,则这种区别变得更加明显:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

可以将进程替换(生成文件)和输入重定向(将文件连接到STDIN)组合在一起:

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

看起来几乎一样,但是这次猫是通过STDIN流而不是文件名传递的。您可以通过使用echo尝试查看:

$ echo < <(date)
<blank>

由于echo不会读取STDIN且未传递任何参数,因此我们什么也没有。

管道和输入重定向将内容推送到STDIN流上。进程替换运行命令,将其输出保存到特殊的临时文件中,然后传递该文件名代替命令。无论您使用什么命令,都将其视为文件名。请注意,创建的文件不是常规文件,而是一个命名管道,一旦不再需要该管道就会自动将其删除。

140
Caleb

这是您可以用进程替换完成的三件事,否则它们是不可能的。

多个过程输入

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

用管道根本无法做到这一点。

保留STDIN

说您有以下几点:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

您想直接运行它。以下失败了。 Bash已经在使用STDIN读取脚本,因此无法进行其他输入。

curl -o - http://example.com/script.sh | bash 

但是这种方式非常有效。

bash <(curl -o - http://example.com/script.sh)

出站流程替代

另请注意,进程替换也可以采用其他方式。因此,您可以执行以下操作:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

这是一个令人费解的示例,但它将stdout发送到/dev/null,同时将stderr传递到sed脚本以提取其名称为显示“权限被拒绝”错误,然后将THOSE结果发送到文件。

请注意,第一个命令和stdout重定向位于括号(subshel​​l)中,以便仅将THAT命令的结果发送到/dev/null,而不会搞砸了其余部分。

26
tylerl

我应该假设您正在谈论bash或其他高级Shell,因为posix Shell没有进程替换

bash手册页报告:

流程替代
支持命名管道(FIFO)或命名打开文件的/ dev/fd方法的系统上支持进程替换。它采用<(list)或>(list)的形式。运行进程列表时,其输入或输出连接到FIFO或/ dev/fd中的某个文件。此文件的名称作为参数的结果作为参数传递给当前命令如果使用>(list)形式,则写入文件将为list提供输入;如果使用<(list)形式,则应读取作为参数传递的文件以获得list的输出。

可用时,进程替换与参数和变量扩展,命令替换和算术扩展同时执行。

换句话说,从实际的角度来看,您可以使用如下表达式

<(commands)

作为其他需要文件作为参数的命令的文件名。或者,您可以对此类文件使用重定向:

while read line; do something; done < <(commands)

回到您的问题,在我看来,过程替换和管道并没有太多共同之处。

如果要按顺序传递多个命令的输出,则可以使用以下形式之一:

(command1; command2) | command3
{ command1; command2; } | command3

但您也可以在流程替换中使用重定向

command3 < <(command1; command2)

最后,如果command3接受文件参数(代替标准输入)

command3 <(command1; command2)
26
enzotib

如果命令以文件列表作为参数并将这些文件作为输入(或输出,但不常见)进行处理,则这些文件中的每个文件都可以是命名管道或/ dev/fd伪文件,由进程替换透明提供:

$ sort -m <(command1) <(command2) <(command3)

这将“传递”三个命令的输出进行排序,因为sort可以在命令行中获取输入文件的列表。

10
camh

应该注意的是,进程替换不限于<(command)的形式,它使用command的输出作为文件。它可以采用>(command)的形式,也将文件作为输入作为command的输入。在@enzotib的答案中bash手册的引用中也提到了这一点。

对于上面的_date | cat_示例,使用>(command)形式的进程替换来达到相同效果的命令是:

_date > >(cat)
_

请注意,>(cat)之前的_>_是必需的。如@Caleb的答案,这可以再次由echo清楚地说明。

_$ echo >(cat)
/dev/fd/63
_

因此,如果没有多余的_>_,date >(cat)将与_date /dev/fd/63_相同,后者将向stderr打印一条消息。

假设您有一个仅将文件名作为参数并且不处理stdinstdout的程序。我将使用过于简化的脚本_psub.sh_进行说明。 _psub.sh_的内容是

_#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"
_

基本上,它将测试其两个参数都是文件(不一定是常规文件),如果是这种情况,请使用awk将_"$1"_的每一行的第一个字段写入_"$2"_。然后,将到目前为止提到的所有内容组合在一起的命令是:

_./psub.sh <(printf "a a\nc c\nb b") >(sort)
_

这将打印

_a
b
c
_

相当于

_printf "a a\nc c\nb b" | awk '{print $1}' | sort
_

但是以下操作无效,我们必须在此处使用流程替换,

_printf "a a\nc c\nb b" | ./psub.sh | sort
_

或其等效形式

_printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort
_

如果_./psub.sh_除了上面提到的以外还读取stdin,则不存在这种等效形式,在这种情况下,除了进程替换,我们无用(当然,您也可以使用命名管道或临时文件,但这是另一回事了。

3
Weijun Zhou