进程替换和管道

Tim*_*Tim 108 shell pipe io-redirection process-substitution

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

将命令的标准输出通过管道传输到另一个命令的标准输入是一种强大的技术。但是,如果您需要通过管道传输多个命令的标准输出怎么办?这就是过程替换的用武之地。

换句话说,进程替换可以做任何管道可以做的事情吗?

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

Cal*_*leb 174

了解它们之间差异的一个好方法是在命令行上做一些实验。尽管<字符的使用在视觉上相似,但它的作用与重定向或管道非常不同。

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

$ date | cat
Thu Jul 21 12:39:18 EEST 2011
Run Code Online (Sandbox Code Playgroud)

这是一个毫无意义的例子,但它表明cat接受了dateon STDIN的输出并将其吐出。通过过程替换可以获得相同的结果:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011
Run Code Online (Sandbox Code Playgroud)

然而,刚刚发生在幕后的事情就不一样了。cat实际传递的不是 STDIN 流,而是它需要打开和读取的文件的名称。您可以使用echo代替来查看此步骤cat

$ echo <(date)
/proc/self/fd/11
Run Code Online (Sandbox Code Playgroud)

当 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
Run Code Online (Sandbox Code Playgroud)

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

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011
Run Code Online (Sandbox Code Playgroud)

它看起来几乎相同,但这次 cat 传递的是 STDIN 流而不是文件名。您可以通过使用 echo 进行尝试来看到这一点:

$ echo < <(date)
<blank>
Run Code Online (Sandbox Code Playgroud)

由于 echo 不读取 STDIN 并且没有传递任何参数,我们什么也得不到。

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

  • @Adobe 您可以确认临时文件进程替换产生的是否是命名管道:`[[ -p &lt;(date) ]] &amp;&amp; echo true`。当我使用 bash 4.4 或 3.2 运行它时,这会产生 `true`。 (3认同)
  • @ctrl-alt-delor 我同意措辞不太好,但我掩盖了 FIFO 的实现细节。从技术上讲,它不是一个“文件”,但它是一个带有文件系统路径的 FIFO 节点,并且它首先被创建,**然后** 管道的其他部分运行。管道也是如此,只是碰巧它们给出的 FIFO 是标准流名称。很高兴接受编辑,使解释保持简单明了,而实际上没有说任何错误。 (3认同)
  • 如果我理解正确,http://tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 说进程替换创建临时文件,而不是命名管道。据我所知,named 不会创建临时文件。写入管道永远不会涉及写入磁盘:/sf/answers/488431961/ (2认同)

tyl*_*erl 34

以下是您可以使用流程替换做的三件事,否则是不可能的。

多个过程输入

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)
Run Code Online (Sandbox Code Playgroud)

根本没有办法用管道来做到这一点。

保留标准输入

假设您有以下内容:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"
Run Code Online (Sandbox Code Playgroud)

你想直接运行它。以下内容惨遭失败。Bash 已经在使用 STDIN 来读取脚本,所以其他输入是不可能的。

curl -o - http://example.com/script.sh | bash 
Run Code Online (Sandbox Code Playgroud)

但是这种方式非常有效。

bash <(curl -o - http://example.com/script.sh)
Run Code Online (Sandbox Code Playgroud)

出站流程替代

另请注意,进程替换也以另一种方式起作用。所以你可以做这样的事情:

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

这是一个有点复杂的示例,但它会将stdout发送到/dev/null,同时将stderr管道传输到 sed 脚本以提取显示“权限被拒绝”错误的文件的名称,然后将 THOSE 结果发送到文件。

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


enz*_*tib 29

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

bash 手册页报告:

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

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

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

<(commands)
Run Code Online (Sandbox Code Playgroud)

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

while read line; do something; done < <(commands)
Run Code Online (Sandbox Code Playgroud)

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

如果要按顺序管道输出多个命令,可以使用以下形式之一:

(command1; command2) | command3
{ command1; command2; } | command3
Run Code Online (Sandbox Code Playgroud)

但您也可以对进程替换使用重定向

command3 < <(command1; command2)
Run Code Online (Sandbox Code Playgroud)

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

command3 <(command1; command2)
Run Code Online (Sandbox Code Playgroud)


cam*_*amh 11

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

$ sort -m <(command1) <(command2) <(command3)
Run Code Online (Sandbox Code Playgroud)

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

  • @Philomath `&lt;()` 与许多高级 shell 功能一样,最初是一个 ksh 功能,后来被 bash 和 zsh 采用。`psub` 是专门的鱼特性,与 POSIX 无关。 (6认同)

Wei*_*hou 6

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

对于date | cat上面的示例,使用表单的进程替换>(command)来实现相同效果的命令将是,

date > >(cat)
Run Code Online (Sandbox Code Playgroud)

请注意,>before>(cat)是必要的。这可以再次通过echo@Caleb 的回答清楚地说明。

$ echo >(cat)
/dev/fd/63
Run Code Online (Sandbox Code Playgroud)

因此,如果没有额外的>,date >(cat)将与date /dev/fd/63将消息打印到标准错误相同。

假设您有一个程序只将文件名作为参数而不处理stdinstdout。我将使用过于简化的脚本psub.sh来说明这一点。的内容psub.sh

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"
Run Code Online (Sandbox Code Playgroud)

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

./psub.sh <(printf "a a\nc c\nb b") >(sort)
Run Code Online (Sandbox Code Playgroud)

这将打印

a
b
c
Run Code Online (Sandbox Code Playgroud)

并且等价于

printf "a a\nc c\nb b" | awk '{print $1}' | sort
Run Code Online (Sandbox Code Playgroud)

但是下面的方法不起作用,我们必须在这里使用进程替换,

printf "a a\nc c\nb b" | ./psub.sh | sort
Run Code Online (Sandbox Code Playgroud)

或其等效形式

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort
Run Code Online (Sandbox Code Playgroud)

如果./psub.sh还读取stdin上面提到的内容,那么这样的等效形式不存在,在这种情况下,我们无法使用任何东西代替进程替换(当然您也可以使用命名管道或临时文件,但那是另一个故事)。