下面的脚本除了说明这个问题之外没有其他目的。
#!/usr/bin/env zsh
arbitrary_pipeline () {
shuf | tr a-z A-Z
}
tmpdir=$( mktemp -d )
mkfifo $tmpdir/{orig,alt}
{ tee $tmpdir/orig | arbitrary_pipeline > $tmpdir/alt; } &
pid=$!
paste $tmpdir/orig $tmpdir/alt
rm -rf $tmpdir
wait $pid
该脚本使用tee
两个命名管道将一些(任意)标准输入拆分为两个流,将其中之一重定向到任意管道,并将生成的两个流的输入传递给paste
.示意图:
STDIN --- > --.- arbitrary_pipeline -.
\ \
paste `----<FIRST-ARGUMENT> `- <SECOND-ARGUMENT> --> STDOUT
(在这种情况下,arbitrary_pipeline
只是打乱其标准输入,并将其转换为大写,但是,顾名思义,它可以是任何东西。)
脚本的 stdout 输出看起来不错,但wait
命令总是失败:
% grep -iP 'z.*s.*h' /usr/share/dict/words | /tmp/test.sh
Nietzsche CITIZENSHIP'S
Zubeneschamali NIETZSCHE
Zubeneschamali's ZUBENESCHAMALI
citizenship CITIZENSHIP
citizenship's ZUBENESCHAMALI'S
/tmp/test.sh:wait:18: pid 26357 is not a child of this shell
我究竟做错了什么?
前言:
/usr/bin/env zsh --version
# zsh 5.0.7 (x86_64-pc-linux-gnu)
编辑:
tee
根据乔丹的建议,在管道周围添加了大括号。 (不过结果没有改变。)- 替换
&!
为&
响应 Stéphane Chazelas 的评论。 (同样,结果没有改变。)
答案1
在 5.0.8 之前,zsh
已经等不及已经死掉的工作了。 2014 年 5.0.8 中对此进行了更改。请参阅更改那里。
在这里,您可以将 stderr 重定向到/dev/null
忽略该问题:
wait $pid 2> /dev/null
请注意,在:
{ tee $tmpdir/orig | arbitrary_pipeline > $tmpdir/alt; } &
作为一种优化,zsh
不会为 分叉额外的进程arbitrary_pipeline
,它将在与运行在后台启动的子 shell 的进程相同的进程中执行它。
paste
在它的 stdin 上看到 EOF 之前不会完成,它的 stdin 是$pid
在另一端写入的管道(及其子项,如果有)。因此,直到$pid
(和子级)将其所有文件描述符(通常只有 stdout)关闭到管道的写入端之前,它不会看到 eof 。除非$pid
明确关闭其标准输出(这是非常不常见的),否则只有在退出时才会发生。
这意味着paste
在大多数情况下,不会在此之前退出,以防万一$pid
仍然是一个好主意。wait
请注意,在这里,您可以使用 acoproc
来避免临时 fifo:
coproc arbitrary_pipeline
cat >&p | paste - /dev/fd/3 3<&p &
coproc : close
wait
(注意,wait
还要等待 s coproc
)。