mengapa tidak bash while loop keluar saat pemipaan ke subcommand yang diakhiri?

12

Mengapa perintah di bawah ini tidak keluar? Alih-alih keluar, loop berjalan tanpa batas.

Sementara saya menemukan perilaku ini menggunakan pengaturan yang lebih kompleks, bentuk perintah yang paling sederhana berkurang menjadi sebagai berikut.

Tidak keluar:

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

Tidak ada salah ketik di atas. Masing-masing '|' adalah sebuah pipa. 'Keluar 1' adalah proses lain yang berjalan dan keluar.

Saya berharap "keluar 1" menyebabkan SIGPIPE pada loop sementara (tulis pada pipa tanpa pembaca) dan untuk loop keluar. Tapi, loop terus berjalan.

Mengapa perintah itu tidak berhenti?

stephen.z
sumber
zsh keluar secara normal.
Braiam

Jawaban:

13

Itu karena pilihan dalam implementasi.

Menjalankan skrip yang sama pada Solaris dengan ksh93menghasilkan perilaku yang berbeda:

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

Apa yang memicu masalah adalah pipa bagian dalam, tanpa itu, loop keluar apa pun shell / OS:

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

cat mendapatkan sinyal SIGPIPE di bawah bash tetapi shell tetap mengulangi 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) ---

Status dokumentasi Bash :

Shell menunggu semua perintah dalam pipa berakhir sebelum mengembalikan nilai.

Dokumentasi Ksh menyatakan:

Setiap perintah, kecuali mungkin yang terakhir, dijalankan sebagai proses terpisah; shell menunggu perintah terakhir untuk mengakhiri.

Status POSIX :

Jika pipa tidak di latar belakang (lihat Daftar Asinkron), shell harus menunggu perintah terakhir yang ditentukan dalam pipa untuk menyelesaikan, dan mungkin juga menunggu semua perintah selesai.

Jlliagre
sumber
Saya pikir itu bukan pipa dalam yang memicu masalah, itu yang echodibangun mengabaikan SIGPIPE. Anda juga dapat mereproduksi masalah dengan menggunakan env echoalih-alih echo(untuk memaksa echobiner yang sebenarnya digunakan). (Bandingkan juga output dari { echo hi; echo $? >&2; } | exit 1dan { env echo hi; echo $? >&2; } | exit 1.)
Lucas Werkmeister
1

Masalah ini telah mengganggu saya selama bertahun-tahun. Terima kasih kepada jilliagre untuk dorongan ke arah yang benar.

Mengulang sedikit pertanyaan, pada kotak linux saya, ini berhenti seperti yang diharapkan:

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

Tetapi jika saya menambahkan pipa, itu tidak berhenti seperti yang diharapkan:

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

Itu membuat saya frustrasi selama bertahun-tahun. Dengan mempertimbangkan jawaban yang ditulis oleh jilliagre, saya menemukan perbaikan yang luar biasa ini:

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

QED ...

Ya tidak cukup. Ini sesuatu yang sedikit lebih rumit:

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

Ini tidak berfungsi dengan baik. Saya menambahkan || exitsehingga ia tahu bagaimana cara mengakhiri lebih awal, tetapi yang pertama echotidak cocok dengan grepbegitu loop berhenti segera. Dalam hal ini, Anda benar-benar tidak tertarik pada status keluar dari grep. Pekerjaan saya adalah menambahkan yang lain cat. Jadi, berikut ini skrip yang dibuat yang disebut "puluhan":

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

Ini benar berakhir ketika dijalankan sebagai tens | head. Terima kasih Tuhan.

PaulC
sumber