Gre*_*bet 10 bash process-substitution
我正在尝试errpipe使用stderr通过过滤器运行的简单 api编写实用程序脚本。起初我尝试使用 bash 的进程替换功能来实现它。
#!/bin/bash
com="$1"
errpipe="$2"
$com 2> >(1>&2 $errpipe)
Run Code Online (Sandbox Code Playgroud)
这样做的问题是当com不存在时输出看起来很奇怪。
如果我输入
sh-3.2$ ./errpipe foo cat
Run Code Online (Sandbox Code Playgroud)
我得到
sh-3.2$ ./errpipe foo cat
sh-3.2$ ./errpipe: line 6: foo: command not found
@
Run Code Online (Sandbox Code Playgroud)
用@代表光标。换句话说,shell 提示打印得太早了。我怀疑这是因为主 shell 脚本没有等待进程替换过程完成。在wait脚本末尾添加a似乎并不能解决问题。
我打开一个解决方案,使用bash,ksh,zsh或可能有些疯狂的awk功能。我想我知道如何使用 C 或 Perl 之类的东西将它们连接在一起,这些东西公开了更丰富的 API 来操作进程和文件描述符,但我想避免使用它,除非没有替代方案。
一种“几乎有效”的解决方案是使用在$$shell fork 时没有改变的事实,并在 errpipe 完成时向父级发出信号。
#!/bin/bash
com="$1"
errpipe="$2"
$com 2> >(1>&2 $errpipe; kill -SIGUSR1 $$)
while true; do
sleep 60
done
Run Code Online (Sandbox Code Playgroud)
这解决了原始问题,但是 a) 很丑,并且 b)User defined signal 1: 30即使我有 SIGUSR1 的信号处理程序,也会在终止之前打印,并且 c) 如果负责向父级发送 SIGUSR 的进程以某种方式死亡,则将永远循环。
Sté*_*las 14
是的,bash就像在ksh(功能来自哪里)一样,进程替换中的进程不需要等待。
对于<(...)一个,这通常很好,例如:
cmd1 <(cmd2)
Run Code Online (Sandbox Code Playgroud)
外壳将等待cmd1并且cmd1通常会cmd2通过读取直到被替换的管道上的文件结束而等待,并且该文件结束通常发生在cmd2死亡时。这与几个 shell(不是bash)不费心等待cmd2in 的原因相同cmd2 | cmd1。
cmd1 >(cmd2)然而,对于,通常情况并非如此,因为它cmd2通常在cmd1那里等待,因此通常会在之后退出。
zsh将在cmd2那里等待(但如果您将其写为则不会cmd1 > >(cmd2),{cmd1} > >(cmd2)而是按照文档中的说明使用)。
ksh默认情况下不等待,但允许您使用wait内置函数等待它(它还使 pid 可用$!,但如果您这样做也无济于事cmd1 >(cmd2) >(cmd3))
rc(使用cmd1 >{cmd2}语法),ksh除了您可以使用$apids.
es(也cmd1 >{cmd2})等待cmd2像zsh,并且还等待cmd2在<{cmd2}过程重定向。
bash确实使cmd2(或更确切地说是子shell的pid,因为它确实cmd2在该子shell的子进程中运行,即使它是那里的最后一个命令)在 中可用$!,但不会让您等待它。
如果您确实必须使用bash,则可以通过使用等待两个命令的命令来解决该问题:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
Run Code Online (Sandbox Code Playgroud)
这使得两者cmd1并cmd2有自己的FD 3开来管。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
Run Code Online (Sandbox Code Playgroud)
确保还有一个额外的 shell 进程打开 fd 3 等待该sudo命令。
请注意,cat不会读取任何内容(因为进程不在其 fd 3 上写入)。它只是为了同步。它将只执行一个read()系统调用,该调用最后什么也没有返回。
您实际上可以cat通过使用命令替换来执行管道同步来避免运行:
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
Run Code Online (Sandbox Code Playgroud)
这一次,它的外壳,而不是cat说从它的另一端是FD 3的开管道读取cmd1和cmd2。我们正在使用变量赋值,因此 的退出状态cmd1在$?.
或者您可以手动进行进程替换,然后您甚至可以使用您的系统,sh因为这将成为标准的 shell 语法:
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
Run Code Online (Sandbox Code Playgroud)
但请注意,如前所述,并非所有sh实现都会cmd1在cmd2完成后等待(尽管这比反过来要好)。那个时候,$?包含退出状态cmd2;虽然bash和zsh化妆cmd1的可用退出状态${PIPESTATUS[0]},并$pipestatus[1]分别(也看到了pipefail在几个炮弹选项,这样$?可以报告管道元件比上次其他故障)
请注意,yash它的进程重定向功能也有类似的问题。cmd1 >(cmd2)会写cmd1 /dev/fd/3 3>(cmd2)在那里。但是cmd2没有等待,您也不能使用wait等待它,并且它的 pid 在$!变量中也不可用。您将使用与bash.