
下面的腳本除了說明這個問題之外沒有其他目的。
#!/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
)。