dlaczego bash podczas pętli nie kończy się podczas potokowania do zakończonej komendy?

12

Dlaczego poniższe polecenie nie wychodzi? Zamiast wyjść, pętla działa w nieskończoność.

Podczas gdy odkryłem to zachowanie za pomocą bardziej złożonej konfiguracji, najprostsza forma polecenia sprowadza się do następujących.

Nie wychodzi:

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

Powyżej nie ma literówek. Każdy „|” jest fajką. „Wyjście 1” oznacza kolejny proces, który został uruchomiony i zakończył się.

Oczekuję, że „wyjście 1” spowoduje SIGPIPE w pętli while (zapisz na potoku bez czytnika) i spowoduje przerwanie się pętli. Ale pętla nadal działa.

Dlaczego polecenie się nie zatrzymuje?

stephen.z
źródło
zsh kończy się normalnie.
Braiam

Odpowiedzi:

13

Wynika to z wyboru w realizacji.

Uruchomienie tego samego skryptu w systemie Solaris z ksh93innym zachowaniem:

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

To, co wyzwala problem, to wewnętrzny potok, bez niego pętla wychodzi bez względu na powłokę / system operacyjny:

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

cat odbiera sygnał SIGPIPE pod bash, ale powłoka i tak iteruje pętlę.

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

Dokumentacja Bash stwierdza:

Powłoka czeka na zakończenie wszystkich poleceń w potoku przed zwróceniem wartości.

Dokumentacja Ksh stwierdza:

Każde polecenie, z wyjątkiem prawdopodobnie ostatniego, jest uruchamiane jako osobny proces; powłoka czeka na zakończenie ostatniego polecenia .

POSIX stwierdza:

Jeśli potok nie znajduje się w tle (patrz Listy asynchroniczne), powłoka musi poczekać na zakończenie ostatniego polecenia określonego w potoku, a także może czekać na zakończenie wszystkich poleceń .

jlliagre
źródło
Myślę, że nie jest to dokładnie wewnętrzny potok, który powoduje problem, ale to, że wbudowane echoignoruje SIGPIPE. Możesz także odtworzyć problem, używając env echozamiast echo(aby wymusić użycie rzeczywistego echopliku binarnego). (Porównaj także wyniki { echo hi; echo $? >&2; } | exit 1i { env echo hi; echo $? >&2; } | exit 1.)
Lucas Werkmeister
1

Ten problem mnie martwił od lat. Dzięki jilliagre za szturchnięcie we właściwym kierunku.

Powtarzając trochę pytanie, na moim Linux-ie, to wychodzi zgodnie z oczekiwaniami:

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

Ale jeśli dodam potok, nie kończy się zgodnie z oczekiwaniami:

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

To frustrowało mnie przez lata. Rozważając odpowiedź napisaną przez Jilliagre, wymyśliłem tę cudowną poprawkę:

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

QED ...

Cóż, niezupełnie. Oto coś nieco bardziej skomplikowanego:

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

To nie działa dobrze. Dodałem || exittak, aby wiedział, jak zakończyć wcześniej, ale pierwszy echonie pasuje do, grepwięc pętla natychmiast się kończy. W takim przypadku naprawdę nie interesuje Cię status wyjścia grep. Moim obejściem jest dodanie kolejnego cat. Oto wymyślony skrypt o nazwie „tens”:

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

To właściwie kończy się, gdy działa jako tens | head. Dzięki Bogu.

PaulC
źródło