我正在尝试编写一个实用程序脚本,其中包含一个通过过滤器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似乎并不能解决问题。
我愿意接受使用bash
、ksh
或zsh
可能某些疯狂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
是的,就像在(功能来自哪里)一样,进程替换内的进程不会被等待bash
。ksh
对于一个<(...)
人来说,这通常很好,如下所示:
cmd1 <(cmd2)
shell 将等待,cmd1
并且cmd1
通常会通过读取来等待,cmd2
直到被替换的管道上的文件结束,并且文件结束通常在cmd2
死亡时发生。这也是几个 shell(不是bash
)不费心等待cmd2
in 的原因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}
等待,并且还等待进程内重定向。cmd2
zsh
cmd2
<{cmd2}
bash
确实使 pid cmd2
(或更准确地说是子 shell 的 pid,因为它确实cmd2
在该子 shell 的子进程中运行,即使它是那里的最后一个命令)在 中可用$!
,但不允许您等待它。
如果您确实必须使用bash
,则可以通过使用将等待这两个命令的命令来解决该问题:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
这使得和 的 fd 3cmd1
都cmd2
向管道开放。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 而不是从另一端在和 的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
makecmd1
的退出状态分别在${PIPESTATUS[0]}
和中可用$pipestatus[1]
(另请参阅pipefail
一些 shell 中的选项,以便$?
可以报告除最后一个之外的管道组件的失败)
请注意,yash
其流程也存在类似问题重定向特征。cmd1 >(cmd2)
会写cmd1 /dev/fd/3 3>(cmd2)
在那里。但cmd2
不等待,您也不能使用wait
等待它,并且它的 pid 也不在$!
变量中可用。您将使用与 相同的解决方法bash
。