我想知道如何理解以下内容:
将一个命令的标准输出通过管道传输到另一个命令的标准输入是一项强大的技术。但是,如果您需要通过管道传输多个命令的标准输出怎么办?这就是流程替代的用武之地。
换句话说,进程替换可以做管道能做的事情吗?
进程替换可以做什么,而管道却不能?
答案1
理解它们之间差异的一个好方法是在命令行上做一些实验。尽管<
角色的使用在视觉上很相似,但它的作用与重定向或管道非常不同。
我们使用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
它看起来几乎相同,但这次 cat 传递的是 STDIN 流而不是文件名。您可以通过使用 echo 尝试来看到这一点:
$ echo < <(date)
<blank>
由于 echo 不读取 STDIN 并且没有传递任何参数,所以我们什么也得不到。
管道和输入重定向将内容推送到 STDIN 流上。进程替换运行命令,将其输出保存到特殊的临时文件,然后传递该文件名来代替命令。无论您使用什么命令,都会将其视为文件名。请注意,创建的文件不是常规文件,而是命名管道,一旦不再需要就会自动删除。
答案2
以下是您可以通过流程替换完成的三件事,否则这些事情是不可能的。
多过程输入
diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)
根本没有办法用管道来做到这一点。
保留标准输入
假设您有以下内容:
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 )
这是一个有点复杂的例子,但它发送了标准输出到/dev/null
, 同时管道标准错误到 sed 脚本以提取显示“权限被拒绝”错误的文件的名称,然后将这些结果发送到文件。
请注意,第一个命令和标准输出重定向位于括号中 (子外壳)这样只有那个命令的结果被发送到/dev/null
并且它不会干扰该行的其余部分。
答案3
我应该假设你正在谈论bash
或其他一些高级 shell,因为 posix shell 没有流程替代。
bash
手册页报告:
流程替代
支持命名管道 (FIFO) 或 /dev/fd 命名打开文件方法的系统支持进程替换。它采用<(列表)或>(列表)的形式。进程列表的输入或输出连接到 FIFO 或 /dev/fd 中的某个文件。该文件的名称作为扩展结果作为参数传递给当前命令。如果使用 >(list) 形式,写入文件将为列表提供输入。如果使用 <(list) 形式,则应读取作为参数传递的文件以获得 list 的输出。
如果可用,进程替换与参数和变量扩展、命令替换和算术扩展同时执行。
换句话说,从实际的角度来看,您可以使用如下所示的表达式
<(commands)
作为需要文件作为参数的其他命令的文件名。或者您可以对这样的文件使用重定向:
while read line; do something; done < <(commands)
回到你的问题,在我看来,进程替换和管道没有太多共同点。
如果要按顺序通过管道传输多个命令的输出,可以使用以下形式之一:
(command1; command2) | command3
{ command1; command2; } | command3
但您也可以在进程替换上使用重定向
command3 < <(command1; command2)
最后,如果command3
接受文件参数(代替标准输入)
command3 <(command1; command2)
答案4
应该注意的是,进程替换并不限于 形式<(command)
,它使用 的输出command
作为文件。它也可以采用>(command)
将文件作为输入的形式。 command
@enzotib 的答案中引用的 bash 手册也提到了这一点。
对于date | cat
上面的示例,使用表单的进程替换>(command)
来实现相同效果的命令将是:
date > >(cat)
请注意,>
前面的内容>(cat)
是必要的。echo
@Caleb 的回答可以再次清楚地说明这一点。
$ echo >(cat)
/dev/fd/63
因此,如果没有额外的>
,将与将消息打印到 stderrdate >(cat)
相同。date /dev/fd/63
假设您有一个程序,仅将文件名作为参数,并且不处理stdin
或stdout
。我将使用过于简化的脚本psub.sh
来说明这一点。的内容psub.sh
是
#!/bin/bash
[ -e "$1" ] && [ -e "$2" ] && awk '{print $1}' < "$1" > "$2"
基本上,它测试它的两个参数是否都是文件(不一定是常规文件),如果是这种情况,则使用 awk 将每一行的第一个字段写入"$1"
to "$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
,那么这样的等效形式不存在,在这种情况下,我们无法使用任何东西来代替进程替换(当然您也可以使用命名管道或临时文件,但这是另一个故事)。