mkfifo foo
printf %s\\n bar | tee foo &
tr -s '[:lower:]' '[:upper:]' <foo
wait
rm foo
Este é um script de shell POSIX funcional do que eu quero fazer:
printf %s\\n bar
é simbólico para um programa externo que produz stdouttr -s '[:lower:]' '[:upper:]'
é simbólico para outro comando que deve receber o stdout e fazer algo com eletee
duplica stdout para pipe nomeado foo
E a saída é como esperado:
bar
BAR
Agora eu gostaria de arrumar o código para que ele se torne external_program | my_function
. Algo assim:
f() (
mkfifo foo
tee foo &
tr -s '[:lower:]' '[:upper:]' <foo
wait
rm foo
)
printf %s\\n bar | f
Mas agora não há saída alguma.
O cerne do problema é que o POSIX tem aquele requisito (geralmente inútil) de
sh
que os comandos assíncronos devem ter seu stdin redirecionado,/dev/null
a menos que haja um redirecionamento stdin explícito (bem, tecnicamente, esse/dev/null
redirecionamento implícito acontece antes dos redirecionamentos explícitos, se houver).Veja, por exemplo, em sistemas baseados em Linux:
Uma solução alternativa comum para que o comando assíncrono não tenha seu stdin alterado é fazer algo como:
Onde o stdin original é disponibilizado no fd 3, além de 0 em um grupo de comando com
3<&0
, e dentro docmd
stdin do grupo de comando, que foi reaberto em/dev/null
porque&
está sendo redirecionado de volta para o stdin original por meio desse fd 3 (que então fechamos porque não é mais necessário).Em:
tee
O stdin do será/dev/null
, não a extremidade de leitura do pipe deprintf
. Alterando para:Iria abordar isso, mas como você descobriu, também o faria
Como ele
tee
não é executado de forma assíncrona, portanto não tem seu stdin redirecionado/dev/null
etr
o stdin do está sendo redirecionado explicitamente, não importa que ele tenha sido redirecionado/dev/null
anteriormente.Também não precisamos do
wait
, poistee
o processo está sendo executado de forma síncrona (atrasado do que foi implicitamente esperado pelo shell) e normalmente não seria encerrado antes,tr
pois está esperando por eof em seu stdin (etr
o stdout de , que é a outra extremidade desse pipe, só é fechado na saída).Talvez você ainda queira esperar
tr
para recuperar seu status de saída:foo
não será removido se o subshell que é o corpo da função for morto. Você pode reduzir a janela durante a qual o fifo existe e torná-lo ainda mais parecido com um pipe sem nome (onde o pipe nomeado é apenas um ponto de encontro efêmero para esses dois processos estabelecerem um pipe), removendo-o assim que for aberto tanto no modo de leitura quanto de gravação pelos dois processos.Parece que o problema é, como suspeito, tentar canalizar
external_program
para um arquivotee
.No roteiro original, havia:
Que não desvincula
tee
(apenas), mas toda a sequência de pipe em uma. Então o que eu originalmente fiz foi um pipe como esse, que é resolvido imediatamente, ou seja, ambossleep
e noop:
são desvinculados juntos:No entanto, ao detaching
tee
dentro da função, foi somentetee
isso que foi detached, não o programa externo no começo do pipe. Reutilizando o exemplo comsleep
, podemos ver que apenas noop:
é detached, massleep
não, e o script leva os 10 segundos completos:A solução não é detach
tee
, mas sim o outro comando:Agora
tee
é possível receber stdout do pipe, escrever no FIFO e a saída da função é novamente: