Eu quero ver twice
a saída duas vezes, mas este script só produzirá uma vez:
dump() {
(sleep 1; cat) > "$1"
}
(sleep 0; echo "twice") | tee >(dump "./a.txt")
echo "$(< "a.txt")"
Para vê-lo duas vezes, tenho que ajustar o tempo de sono:
dump() {
(sleep 0; cat) > "$1"
}
(sleep 1; echo "twice") | tee >(dump "./a.txt")
echo "$(< "a.txt")"
O que causou a condição de corrida aqui?
A
dump
chamada na substituição do processo está sendo executada como um processo assíncrono. Isso significa quetee
grava sua saída nele e, em seguida, os pipelines são concluídos. O pipeline termina porque a saída detee
é armazenada em buffer; se você escrevesse mais dados do que o tamanho do buffer do pipe ,tee
teria que esperar paradump
consumi-lo, e seu código original provavelmente funcionaria.Supondo que você escreva apenas uma pequena quantidade de dados, como você faz na pergunta, você lê
a.txt
imediatamente após o término do pipeline, antesdump
de ter a chance de gravar qualquer coisa no arquivo (ele ainda está dormindo em segundo plano, com os dados pendurados ao redor em um buffer de tubulação).Se você examinar o
a.txt
arquivo depois de executar o código defeituoso, notará que ele contém a stringtwice
. Então, ele chega lá eventualmente, após o pequeno atraso fornecido pelasleep 1
função.Para impedir que o pipeline termine muito cedo, adicione um
cat
no final:Isso faz com que funcione porque agora o
cat
processo precisa esperar que a saída dedump
chegue pelo pipe (não haverá nenhuma, mas ele não sabe disso). Isso atrasa o término do pipeline até que adump
chamada retorne. Nesse ponto, os dados já foram gravadosa.txt
e podem ser selecionados pelo último comando no script.A única coisa que sincroniza os processos em um pipeline é a E/S, ou seja, ler dados do processo anterior e gravar no próximo. Se um processo espera ler da etapa anterior em algum momento, ele bloqueará até que algo esteja disponível para ser lido ou até que a etapa anterior tenha fechado sua extremidade do tubo.
cat
lê da entrada padrão por padrão. A entrada padrão do adicionadocat
é conectada à saída padrãotee
e à substituição do processodump
na etapa anterior do pipeline. Ocat
utilitário lerá até que não haja mais nada para ler. Isso não acontece até que ambostee
edump
terminem de executar.Versão limpa do código:
IIUC, a questão é como esperar que o processo interno
>(...)
termine antes de executar a$(...)
substituição do comando da próxima linha.A resposta é que não há uma maneira legal de fazer isso. Se o seu sistema suporta o
/dev/fd/
mecanismo, você pode usar umexec fd> >(...)
truque:Sim, isso é muito feio, mas você pode fazer ainda pior:
Como pode ser coletado para isso, a) com versões mais recentes do bash (>= 5.0) você pode
wait
executar os processos internos>(...)
e b) esses processos podem não terminar até obter um EOF em seu stdin, o que não acontecerá até você feche a outra extremidade do tubo - o/dev/fd/63
ou similar para o>(...)
qual se expandiu. Este último é bastante complicado de acertar.Algumas versões do Bash têm um bug de modo que não aguardam corretamente os processos gerados pela substituição do processo.