Answers:
解决它们之间差异的一个好方法是在命令行上做一些试验。尽管在<
字符使用上具有视觉相似性,但它的功能与重定向或管道非常不同。
让我们使用date
命令进行测试。
$ date | cat
Thu Jul 21 12:39:18 EEST 2011
这是一个毫无意义的示例,但是它表明cat
接受了date
STDIN上的输出并将其吐出。通过流程替换可以达到相同的结果:
$ 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流上。进程替换运行命令,将其输出保存到特殊的临时文件中,然后传递该文件名代替命令。无论您使用什么命令,都将其视为文件名。请注意,创建的文件不是常规文件,而是一个命名管道,一旦不再需要该管道便会自动删除。
[[ -p <(date) ]] && echo true
。true
当我使用bash 4.4或3.2运行它时,会产生这种情况。
我应该假设您正在谈论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)
这是您可以用进程替换完成的三件事,否则它们是不可能的。
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 )
这是一个令人费解的示例,但是它将stdout发送到/dev/null
,同时将stderr用管道传输到sed脚本以提取显示了“权限被拒绝”错误的文件的名称,然后将THOSE结果发送到文件。
请注意,第一个命令和stdout重定向位于括号(subshell)中,以便仅将THAT命令的结果发送至/dev/null
该命令,并且不会与其余行混淆。
diff
示例中,您可能要关心cd
可能失败的情况:diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls)
。
如果命令以文件列表作为参数并将这些文件作为输入(或输出,但不常见)进行处理,则这些文件中的每一个都可以是进程替换透明提供的命名管道或/ dev / fd伪文件:
$ sort -m <(command1) <(command2) <(command3)
这将“传递”三个命令的输出进行排序,因为sort可以在命令行中获取输入文件的列表。
<()
与许多高级Shell功能一样,@Philomath 最初是ksh功能,被bash和zsh所采用。psub
特别是鱼功能,与POSIX无关。
应该注意的是,进程替换不限于form <(command)
,它使用的输出command
作为文件。它也可以采用将>(command)
文件作为输入的形式command
。在@enzotib的答案中bash手册的引用中也提到了这一点。
对于date | cat
上面的示例,使用表单的流程替换>(command)
来达到相同效果的命令是,
date > >(cat)
请注意,>
之前>(cat)
是必要的。echo
如@Caleb的答案所示,这可以再次清楚地说明。
$ echo >(cat)
/dev/fd/63
因此,如果没有多余的内容>
,date >(cat)
它将与date /dev/fd/63
向stderr打印一条消息一样。
假设您有一个仅将文件名作为参数而不处理stdin
或的程序stdout
。我将使用过于简化的脚本psub.sh
进行说明。的内容psub.sh
是
#!/bin/bash
[ -e "$1" -a -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
之外还读取其他内容,那么就不存在这种等效形式,在这种情况下,除了进程替换之外,我们什么都不能使用(当然,您也可以使用命名管道或临时文件,但这是另一种形式)故事)。