Bash: não pode sair do loop canalizado “while read”; trabalhos de substituição de processos

6

Eu pretendo enviar a saída de um programa para um while read VAR loop e break quando um padrão for encontrado, mas isso não acontece.

Prova de conceito:

inotifywait -qm -e create . | while read line; do echo $line; break; done
./ CREATE newfile

..

tail -f /var/log/syslog | while read line; do echo $line; break; done
Nov 6 22:44:05 section9 ntpdate[2381]: adjust time server 91.189.89.199 offset 0.272779 sec

Estes nunca saem, não importa o que o programa de origem produz. A definição de set -x sugere que o loop nunca será repetido para o segundo read . $BASH_SUBSHELL é 1 nestes exemplos.

O tail , inotifywait , etc. não deve receber SIGPIPE e sair?

Observe que a substituição do processo ( while read ... break; done < <(tail -f ...) ) funciona bem. $BASH_SUBSHELL é 0 neste caso.

    
por arielCo 07.11.2014 / 06:12

2 respostas

9

A chave é que, sob o bash (outros shells podem diferir), um pipeline não é terminado até que todos os comandos no pipeline sejam finalizados.

Para entender, vamos considerar:

inotifywait -qm -e create . | while read line; do echo $line; break; done

Quando read lê uma linha, é ecoada e, em seguida, break é executado e o último processo é finalizado. O primeiro processo, no entanto, continua até que uma gravação no stdout falhe. Assim, o loop continuará pelo menos até que inotifywait tente gravar sua linha de saída segundo . Por causa dos caprichos do buffer, isso pode não acontecer até então. Pode demorar várias linhas antes da descoberta ocorrer. Quando essa tentativa de gravação falha, o SIGPIPE é emitido.

Agora, considere o outro caso:

while read line; do echo $line; break; done < <(inotifywait -qm -e create .)

Aqui, não há pipeline. Quando break é executado, o loop while é concluído.

Documentação

Na seção "Pipelines" de man bash :

The shell waits for all commands in the pipeline to terminate....

Esse comportamento é uma escolha feita por bash . Não é obrigatório pelo POSIX. POSIX afirma:

If the pipeline is not in the background (see Asynchronous Lists), the shell shall wait for the last command specified in the pipeline to complete, and may also wait for all commands to complete.

    
por 07.11.2014 / 07:02
2
strace tail -f -n 5000 /var/log/messages | 
    while read line; do echo $line; break; done;

mostra em detalhes o que acontece:

[...]
write(1, "Nov  1 22:20:01 inno systemd[1]:"..., 4096) = 4096

i.e. cauda escreve até 4096 bytes de uma vez para o pipeline (é isso que um pipeline pode armazenar em buffer). Geralmente não há muitos dados para tail :

strace tail -f /var/log/messages | while read line; do echo $line; break; done;

mostra

write(1, "Nov  7 07:00:02 inno systemd[1]:"..., 730) = 730

Assim, tail não tentaria escrever novamente até que algo seja anexado ao arquivo.

Eu achei que unbuffer resolveria esse problema. Estou surpreso que isso não ocorra.

unbuffer tail -f file1 | while read line; do echo $line; break; done
    
por 07.11.2014 / 07:16

Tags