por que um loop bash while não sai ao canalizar para o subcomando finalizado?

12

Por que o comando abaixo não sai? Em vez de sair, o loop é executado indefinidamente.

Embora eu tenha descoberto esse comportamento usando uma instalação mais complexa, a forma mais simples do comando se reduz ao seguinte.

Não sai:

while /usr/bin/true ; do echo "ok" | cat ; done | exit 1

Não há erros de digitação acima. Cada '|' é um cano. A 'saída 1' representa outro processo que foi executado e encerrado.

Eu espero que a "saída 1" cause um SIGPIPE no loop while (escreva em um pipe sem leitor) e que o loop saia. Mas, o loop continua em execução.

Por que o comando não para?

stephen.z
fonte
O zsh sai normalmente.
Braiam

Respostas:

13

É devido a uma escolha na implementação.

A execução do mesmo script no Solaris com ksh93produz um comportamento diferente:

$ while /usr/bin/true ; do echo "ok" | cat ; done | exit 1
cat: write error [Broken pipe]

O que desencadeia o problema é o pipeline interno; sem ele, o loop sai independentemente do shell / OS:

$ while /usr/bin/true ; do echo "ok" ; done | exit 1
$

cat está recebendo um sinal SIGPIPE no bash, mas o shell está iterando o loop de qualquer maneira.

Process 5659 suspended
[pid 28801] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28801] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28801 detached
Process 28800 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28802 attached
Process 28803 attached
[pid 28803] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
Process 5659 suspended
[pid 28803] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28803 detached
Process 28802 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28804 attached
Process 28805 attached (waiting for parent)
Process 28805 resumed (parent 5659 ready)
Process 5659 suspended
[pid 28805] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28805] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28805 detached
Process 28804 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

A documentação do Bash indica:

O shell aguarda a conclusão de todos os comandos no pipeline antes de retornar um valor.

A documentação do Ksh afirma:

Cada comando, exceto possivelmente o último, é executado como um processo separado; o shell aguarda o último comando terminar.

POSIX afirma:

Se o pipeline não estiver em segundo plano (consulte Listas assíncronas), o shell aguardará a conclusão do último comando especificado no pipeline e também poderá aguardar a conclusão de todos os comandos .

jlliagre
fonte
Eu acho que não é exatamente o pipeline interno que desencadeia o problema, é que ele echoignora o SIGPIPE. Você também pode reproduzir o problema usando em env echovez de echo(para forçar o echobinário real a ser usado). (Compare também a saída do { echo hi; echo $? >&2; } | exit 1e { env echo hi; echo $? >&2; } | exit 1.)
Lucas Werkmeister
1

Esse problema me incomoda há anos. Obrigado a Jilliagre pela cutucada na direção certa.

Reafirmando um pouco a pergunta, na minha caixa Linux, isso é encerrado como esperado:

while true ; do echo "ok"; done | head

Mas se eu adicionar um pipe, ele não será encerrado como esperado:

while true ; do echo "ok" | cat; done | head

Isso me frustrou por anos. Ao considerar a resposta escrita por jilliagre, criei esta correção maravilhosa:

while true ; do echo "ok" | cat || exit; done | head

QED ...

Bem, não exatamente. Aqui está algo um pouco mais complicado:

i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' || exit
done | head

Isso não funciona direito. Eu adicionei o || exitpara que ele saiba como terminar mais cedo, mas o primeiro echonão corresponde ao, grepentão o loop é encerrado imediatamente. Nesse caso, você realmente não está interessado no status de saída do grep. Minha solução alternativa é adicionar outra cat. Então, aqui está um script artificial chamado "dezenas":

#!/bin/bash
i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' | cat || exit
done

Isso termina corretamente quando executado como tens | head. Graças a Deus.

PaulC
fonte