即使命令无效,bash也会在进程替换中等待进程

即使命令无效,bash也会在进程替换中等待进程

我正在尝试编写一个实用程序脚本,其中包含一个通过过滤器errpipe运行的简单 api 。stderr起初我尝试使用 bash 的进程替换功能来实现它。

#!/bin/bash

com="$1"
errpipe="$2"

$com 2> >(1>&2 $errpipe)

com这样做的问题是,当不存在时,输出看起来很奇怪。

如果我输入

sh-3.2$ ./errpipe foo cat

我明白了

sh-3.2$ ./errpipe foo cat
sh-3.2$ ./errpipe: line 6: foo: command not found
@

with@代表光标。换句话说,shell 提示符打印得太早了。我怀疑这是因为主 shell 脚本没有等待进程替换过程完成。在wait脚本末尾添加 a似乎并不能解决问题。

我愿意接受使用bashkshzsh可能某些疯狂awk功能的解决方案。我想我知道如何使用 C 或 Perl 之类的东西将它们连接在一起,这些东西公开了更丰富的 API 来操作进程和文件描述符,但我想避免使用它,除非没有其他选择。


一种“几乎有效”的解决方案是利用 shell 分叉时不会更改的事实,$$并在 errpipe 完成时向父级发出信号。

#!/bin/bash

com="$1"
errpipe="$2"

$com 2> >(1>&2 $errpipe; kill -SIGUSR1 $$)

while true; do
    sleep 60
done

这解决了原来的问题,但是a)很丑陋,b)User defined signal 1: 30即使我有 SIGUSR1 的信号处理程序,也会在终止之前打印,而c)如果负责向父进程发送 SIGUSR 的进程以某种方式终止,则将永远循环。

答案1

是的,就像在(功能来自哪里)一样,进程替换内的进程不会被等待bashksh

对于一个<(...)人来说,这通常很好,如下所示:

cmd1 <(cmd2)

shell 将等待,cmd1并且cmd1通常会通过读取来等待,cmd2直到被替换的管道上的文件结束,并且文件结束通常在cmd2死亡时发生。这也是几个 shell(不是bash)不费心等待cmd2in 的原因cmd2 | cmd1

然而,对于cmd1 >(cmd2),情况通常并非如此,因为它通常会在那里cmd2等待,cmd1因此通常会在之后退出。

zsh将在那里等待cmd2(但如果你将其写为cmd1 > >(cmd2),则不会,而是使用{cmd1} > >(cmd2)as有记录的)。

ksh默认情况下不等待,但让您使用内置等待它wait(它也使 pid 可用$!,尽管如果您这样做则没有帮助cmd1 >(cmd2) >(cmd3)

rc(使用cmd1 >{cmd2}语法),与其他相同,ksh只是您可以使用 . 获取所有后台进程的 pid $apids

es(也与)类似 incmd1 >{cmd2}等待,并且还等待进程内重定向。cmd2zshcmd2<{cmd2}

bash确实使 pid cmd2(或更准确地说是子 shell 的 pid,因为它确实cmd2在该子 shell 的子进程中运行,即使它是那里的最后一个命令)在 中可用$!,但不允许您等待它。

如果您确实必须使用bash,则可以通过使用将等待这两个命令的命令来解决该问题:

{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1

这使得和 的 fd 3cmd1cmd2向管道开放。cat将等待另一端的文件结尾,因此通常仅在 和 都死亡时cmd1退出cmd2。 shell 将等待该cat命令。您可以将其视为捕获所有后台进程终止的网络(您可以将其用于在后台启动的其他操作,例如 with &、 coprocs 甚至是后台本身的命令,前提是它们不会像守护进程通常那样关闭所有文件描述符)。

请注意,由于上面提到的浪费的子 shell 进程,即使cmd2关闭其 fd 3,它仍然可以工作(命令通常不会这样做,但有些喜欢sudo或这样ssh做)。未来的版本bash最终可能会像其他 shell 一样进行优化。那么你需要类似的东西:

{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1

确保仍有一个额外的 shell 进程打开 fd 3 等待该sudo命令。

请注意,cat不会读取任何内容(因为进程不会在其 fd 3 上写入)。它只是用于同步。它只会执行一次read()系统调用,最终不会返回任何内容。

实际上,您可以cat通过使用命令替换来进行管道同步来避免运行:

{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1

这次,是 shell 而不是从另一端在和 的catfd 3 上打开的管道中读取数据。我们使用变量赋值,因此 的退出状态在 中可用。cmd1cmd2cmd1$?

或者您可以手动进行进程替换,然后您甚至可以使用系统的,sh因为这将成为标准 shell 语法:

{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1

但请注意,如前所述,并非所有sh实现都会等待完成cmd1cmd2(尽管这比相反更好)。此时,$?包含 的退出状态cmd2;虽然bashzshmakecmd1的退出状态分别在${PIPESTATUS[0]}和中可用$pipestatus[1](另请参阅pipefail一些 shell 中的选项,以便$?可以报告除最后一个之外的管道组件的失败)

请注意,yash其流程也存在类似问题重定向特征。cmd1 >(cmd2)会写cmd1 /dev/fd/3 3>(cmd2)在那里。但cmd2不等待,您也不能使用wait等待它,并且它的 pid 也不在$!变量中可用。您将使用与 相同的解决方法bash

相关内容