Em:
cmd1 | cmd2
cmd2
não morre automaticamente quando cmd1
termina, no entanto, ele verá o final do arquivo em seu stdin. E é assim que os pipelines costumam chegar ao fim.
Em:
echo foo | sed s/o/e/g
sed
será encerrado após echo
ter terminado, porque descobre que não há mais a ler de seu stdin.
Você poderia tentar e ter cmd1
kill cmd2
na finalização, mas e se cmd2
não tivesse lido tudo que cmd1
escreveu no pipe ainda?
Se cmd2
morrer primeiro, cmd1
não morrerá automaticamente, mas será morto (com um SIGPIPE) na próxima vez que tentar gravar no canal (agora quebrado).
É assim que os pipelines gostam:
yes | head
terminate ( yes
é eliminado após a primeira gravação que faz no pipe após head
ter terminado depois de ler e imprimir as primeiras 10 linhas).
Agora, se você realmente quisesse matar processos na outra extremidade do canal, não há nenhuma maneira portátil de descobrir quais processos têm um descritor de arquivos para a outra extremidade de um canal.
No Linux, você pode procurar os arquivos em /proc/*/fd
que têm o mesmo inode do pipe no fd até o final do pipe e onde as permissões do symlink em si indicam qual é o final do pipe.
$ (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]
O mesmo pipe inode e o fd até o fim da escrita tem w
de permissões, enquanto o que está no final da leitura tem r
permissões.
Por exemplo, com zsh
você pode obter os pids dos processos que possuem um fd na extremidade de leitura de um canal cuja outra extremidade está em nosso fd 3 com:
pids=(/proc/<->/fd/*(Nf{u+r}e{'[[ $REPLY -ef /dev/fd/3 ]]'}-p:h:h:t))
pids=(${(u)pids}) # unique
Exemplo:
$ (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
Então você pode fazer (ainda no Linux e com 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
}
E então:
run_and_kill_the_other_end node a.js | run_and_kill_the_other_end node b.js
Mais uma vez, provavelmente não é isso que você quer fazer. Por exemplo:
$ 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
foi morto antes de ter a chance de escrever sua saída classificada. wc -l
conseguiu escapar.
Você pode inserir um atraso como período de tolerância, mas por quanto tempo deve ser o atraso? Por exemplo, se eu adicionar um sleep 0.1
após o exec >&-
, vejo:
$ 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
Isso também significa atrasar o término do pipeline incondicionalmente. Você pode melhorar isso usando zsh
de zselect
com um tempo limite.