![终止管道中的其他进程(以编程方式)](https://linux22.com/image/118290/%E7%BB%88%E6%AD%A2%E7%AE%A1%E9%81%93%E4%B8%AD%E7%9A%84%E5%85%B6%E4%BB%96%E8%BF%9B%E7%A8%8B%EF%BC%88%E4%BB%A5%E7%BC%96%E7%A8%8B%E6%96%B9%E5%BC%8F%EF%BC%89.png)
假设我有这个:
node a.js | node b.js | node c.js
如果a.js快死了,我可以杀掉b.js吗?反之又如何呢?似乎如果我建立这个管道,一个进程不一定会在另一个进程死亡/退出时死亡/退出。如果一个人死了,我怎么能把他们全部杀掉呢?
答案1
在:
cmd1 | cmd2
cmd2
完成后不会自动终止cmd1
,但是,它会在其标准输入上看到文件结尾。管道通常就是这样结束的。
在:
echo foo | sed s/o/e/g
sed
将在终止后退出echo
,因为它发现此时没有更多内容可从其标准输入中读取。
您可以尝试在终止时cmd1
终止cmd2
,但如果cmd2
尚未读取cmd1
写入管道的所有内容怎么办?
如果cmd2
先死,cmd1
则不会自动死,但下次尝试写入(现已损坏的)管道时,它将被杀死(使用 SIGPIPE)。
管道就是这样的:
yes | head
终止(在读取和打印前 10 行后终止后,yes
第一次写入管道时被终止)。head
现在,如果您确实想终止管道另一端的进程,则没有可移植的方法来找出哪些进程具有管道另一端的文件描述符。
/proc/*/fd
在 Linux 上,您可以在管道末端的 fd 上查找与管道具有相同 inode 的文件,并且符号链接本身的权限指示它是管道的哪一端。
$ (ls -lLi /proc/self/fd/3; ls -l /proc/self/fd/3) 3>&1 >&2 | (ls -Lil /proc/self/fd/0; ls -l /proc/self/fd/0)
224052 prw------- 1 chazelas chazelas 0 Sep 20 23:26 /proc/self/fd/3|
224052 prw------- 1 chazelas chazelas 0 Sep 20 23:26 /proc/self/fd/0|
l-wx------ 1 chazelas chazelas 64 Sep 20 23:26 /proc/self/fd/3 -> pipe:[224052]
lr-x------ 1 chazelas chazelas 64 Sep 20 23:26 /proc/self/fd/0 -> pipe:[224052]
同一个管道inode,写端的fd有w
权限,读端的fd有r
权限。
例如,zsh
您可以使用以下命令获取在管道读取端具有 fd 的进程的 pid,该管道的另一端位于我们的 fd 3 上:
pids=(/proc/<->/fd/*(Nf{u+r}e{'[[ $REPLY -ef /dev/fd/3 ]]'}-p:h:h:t))
pids=(${(u)pids}) # unique
例子:
$ (pids=(/proc/<->/fd/*(Nf{u+r}e{'[[ $REPLY -ef /dev/fd/3 ]]'}-p:h:h:t))
pids=(${(u)pids}); ps -fp $pids) 3>&1 >&2 | tr a b
UID PID PPID C STIME TTY TIME CMD
chazelas 24157 11759 0 23:41 pts/1 00:00:00 tr a b
所以你可以这样做(仍然在Linux上并使用zsh
):
run_and_kill_the_other_end() {
setopt localoptions localtraps
trap : TERM
"$@"
local ret=$?
local -aU pids
if [ -p /dev/stdout ]; then
pids=(/proc/<2->/fd/<0-9>(Nf{u+r}e{'[[ $REPLY -ef /dev/stdout ]]'}-p:h:h:t))
exec >&- # give the right end a chance to see eof and act upon it
fi
[ -p /dev/stdin ] &&
pids+=(/proc/<2->/fd/<0-9>(Nf{u+w}e{'[[ $REPLY -ef /dev/stdin ]]'}-p:h:h:t))
(($#pids)) && kill $pids 2> /dev/null
return $ret
}
进而:
run_and_kill_the_other_end node a.js | run_and_kill_the_other_end node b.js
不过,这可能不是您想要做的。例如:
$ run_and_kill_the_other_end seq 100 |
run_and_kill_the_other_end sort |
run_and_kill_the_other_end wc -l
0
sort
在有机会写入排序输出之前就被杀死了。wc -l
设法逃脱。
您可以插入延迟作为宽限期,但延迟应该多长?例如,如果我在sleep 0.1
后面添加exec >&-
,我会看到:
$ run_and_kill_the_other_end seq 100 | run_and_kill_the_other_end sort | run_and_kill_the_other_end wc -l
100
$ run_and_kill_the_other_end seq 10000 | run_and_kill_the_other_end sort | run_and_kill_the_other_end wc -l
10000
$ run_and_kill_the_other_end seq 100000 | run_and_kill_the_other_end sort | run_and_kill_the_other_end wc -l
0
这也意味着无条件延迟管道的终止。您可以通过使用带有超时的zsh
's来改进这一点。zselect