perché un loop bash while termina quando si esegue il piping al sottocomando terminato?


12

Perché il comando qui sotto non termina? Invece di uscire, il ciclo si avvia indefinitamente.

Mentre ho scoperto questo comportamento usando una configurazione più complessa, la forma più semplice del comando si riduce a quanto segue.

Non esce:

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

Non ci sono errori di battitura sopra. Ogni '|' è una pipa. L '"uscita 1" sostituisce un altro processo eseguito ed chiuso.

Mi aspetto che l '"uscita 1" provochi un SIGPIPE sul ciclo while (scrivere su una pipe senza lettore) e che il ciclo si interrompa. Ma il ciclo continua a funzionare.

Perché il comando non si ferma?


zsh esce normalmente.
Braiam

Risposte:


13

È dovuto a una scelta nell'attuazione.

L'esecuzione dello stesso script su Solaris con ksh93produce un comportamento diverso:

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

Ciò che innesca il problema è la pipeline interna, senza di essa, il loop esce da qualunque shell / sistema operativo:

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

cat sta ricevendo un segnale SIGPIPE sotto bash ma la shell sta comunque ripetendo il loop.

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) ---

La documentazione di Bash afferma:

La shell attende che tutti i comandi nella pipeline terminino prima di restituire un valore.

La documentazione di Ksh afferma:

Ogni comando, tranne forse l'ultimo, viene eseguito come processo separato; la shell attende che l'ultimo comando termini.

POSIX afferma:

Se la pipeline non è in background (consultare Elenchi asincroni), la shell deve attendere il completamento dell'ultimo comando specificato nella pipeline e può anche attendere il completamento di tutti i comandi .


Penso che non sia esattamente la pipeline interna che scatena il problema, è che builtin echoignora SIGPIPE. È inoltre possibile riprodurre il problema utilizzando env echoinvece di echo(per forzare l' utilizzo del file echobinario effettivo ). (Confronta anche l'output di { echo hi; echo $? >&2; } | exit 1e { env echo hi; echo $? >&2; } | exit 1.)
Lucas Werkmeister

1

Questo problema mi ha infastidito per anni. Grazie a jilliagre per la spinta nella giusta direzione.

Ribadendo un po 'la domanda, sulla mia scatola di Linux, questo si chiude come previsto:

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

Ma se aggiungo una pipe, non si chiude come previsto:

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

Questo mi ha frustrato per anni. Considerando la risposta scritta da jilliagre, ho trovato questa soluzione meravigliosa:

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

QED ...

Bene, non proprio. Ecco qualcosa di un po 'più complicato:

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

Questo non funziona bene. Ho aggiunto il || exitmodo in cui sa come terminare presto, ma il primo echonon corrisponde al grepquindi il ciclo si chiude immediatamente. In questo caso, non sei davvero interessato allo stato di uscita di grep. La mia soluzione è aggiungerne un altro cat. Quindi, ecco uno script inventato chiamato "decine":

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

Questo termina correttamente quando eseguito come tens | head. Grazie Dio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.