por que um laço bash while não sai quando o piping termina no subcomando?

11

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

Embora eu tenha descoberto esse comportamento usando uma configuraçã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" substitui outro processo que saiu e saiu.

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

Por que o comando não pára?

    
por stephen.z 04.01.2014 / 08:34

2 respostas

13

É devido a uma escolha na implementação.

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

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

O que dispara o problema é o pipeline interno, sem ele, o loop sai de qualquer shell / SO:

$ 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 declara:

The shell waits for all commands in the pipeline to terminate before returning a value.

A documentação do

Ksh declara:

Each command, except possibly the last, is run as a separate process; the shell waits for the last command to terminate.

POSIX declara:

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 04.01.2014 / 09:31
0

Esse problema me incomoda há anos. Graças a jilliagre para o empurrão na direção certa.

Reafirmando um pouco a questão, 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, descobri essa solução maravilhosa:

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

Q.E.D. ...

Bem, não é bem assim. 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 bem. Eu adicionei o || exit para que ele saiba como terminar cedo, mas o primeiro echo não corresponde ao grep , então o loop sai imediatamente. Nesse caso, você não está realmente interessado no status de saída do grep . Minha solução é adicionar outro cat . Então, aqui está um script inventado 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.

    
por 17.11.2016 / 18:41