是的,bash
就像ksh
(功能来自何处)一样,不等待进程替换内的进程(在脚本中运行下一个命令之前)。
对于<(...)
一个,通常如下所示:
cmd1 <(cmd2)
外壳程序将等待,cmd1
并且cmd1
通常会cmd2
由于其读取而等待,直到被替换的管道上的文件结束为止,并且文件结束通常在cmd2
死时发生。这就是几个shell(非bash
)不用费心等待cmd2
in 的相同原因cmd2 | cmd1
。
对于cmd1 >(cmd2)
,然而,这通常不是这样的,因为它更cmd2
通常等待cmd1
一般会退出后,有那么。
这是固定的,zsh
因为它在cmd2
那里等待(但是如果您将其编写为cmd1 > >(cmd2)
且cmd1
不是内置的,则不行,{cmd1} > >(cmd2)
而是按记录使用)。
ksh
默认情况下不等待,但是让您使用wait
内置命令等待它(它也使pid在中可用$!
,尽管这样做对您没有帮助cmd1 >(cmd2) >(cmd3)
)
rc
(使用cmd1 >{cmd2}
语法),ksh
除了可以使用获取所有后台进程的pids之外$apids
。
es
(也cmd1 >{cmd2}
)等待cmd2
像zsh
,并且还等待cmd2
在<{cmd2}
过程重定向。
bash
确实在中提供了的pid cmd2
(或更确切地说,是在该子shell cmd2
的子进程中运行的pid,即使它是那里的最后一个命令)$!
,但不允许您等待它。
如果必须使用bash
,则可以通过使用以下命令来等待两个命令来解决此问题:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
这使两者cmd1
并cmd2
使其fd 3通向管道。cat
将等待最终的文件在另一端,所以通常只退出时都cmd1
和cmd2
都死了。Shell将等待该cat
命令。您可能会看到它是一个捕获所有后台进程终止的网络(您可以将其用于在后台启动的其他事情,例如with &
,coprocs甚至是后台自身的命令,只要它们不像守护进程那样关闭所有文件描述符)。
请注意,由于上面提到的浪费的subshell进程,即使cmd2
关闭fd 3也可以运行(命令通常不执行此操作,但有些命令喜欢sudo
或ssh
执行)。将来的版本bash
可能最终会像其他Shell一样在那里进行优化。然后,您将需要以下内容:
{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
为了确保在打开fd 3时还有一个额外的Shell进程,等待该sudo
命令。
请注意,它cat
不会读取任何内容(因为进程不会在其fd 3上进行写入)。它只是用于同步。它只会执行一个read()
系统调用,最后将不返回任何内容。
您实际上可以cat
通过使用命令替换执行管道同步来避免运行:
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
这一次,它的外壳,而不是cat
说从它的另一端是FD 3的开管道读取cmd1
和cmd2
。我们使用的是变量分配,因此中的退出状态cmd1
可用$?
。
或者,您可以手动进行流程替换,然后甚至可以使用系统的sh
语法,因为这将成为标准的Shell语法:
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
尽管请注意,如前所述,并非所有sh
实现都将cmd1
在cmd2
完成后等待(尽管比其他方法要好)。当时,$?
包含的退出状态cmd2
;不过bash
,zsh
并使cmd1
的退出状态分别在${PIPESTATUS[0]}
和中可用$pipestatus[1]
(另请参见pipefail
一些shell中的选项,因此$?
可以报告除最后一个管道组件之外的管道组件的故障)
请注意,yash
其过程重定向功能也有类似的问题。cmd1 >(cmd2)
将被写cmd1 /dev/fd/3 3>(cmd2)
在那里。但是cmd2
不等待,您也不能wait
等待它,并且它的pid也不在$!
变量中可用。您将使用与相同的解决方法bash
。