Mengapa menggunakan `yes` pada bash pipeline * tidak * menyebabkan loop tak terbatas?

16

Menurut dokumentasinya, bash menunggu hingga semua perintah dalam pipa selesai berjalan sebelum melanjutkan

Shell menunggu semua perintah dalam pipa untuk berakhir sebelum mengembalikan nilai.

Jadi mengapa perintah yes | trueselesai segera? Bukankah seharusnya yesloop selamanya dan menyebabkan pipa tidak pernah kembali?


Dan sebuah subquestion: menurut spec POSIX , shell pipelines dapat memilih untuk kembali setelah perintah terakhir selesai atau menunggu sampai semua perintah selesai. Apakah kerang biasa memiliki perilaku yang berbeda dalam pengertian ini? Apakah ada cangkang di mana yes | trueakan berulang selamanya?

hugomg
sumber
yes | tee >(true) >/dev/nullakan melakukan apa yang Anda harapkan, btw, seperti yang teeberlanjut sampai semua penulis mati, sehingga truekeluar tidak akan mengganggu sepenuhnya.
Charles Duffy
1
truepada dasarnya adalah sebuah {return 0;}program, jadi saya tidak akan berharap itu berjalan lama, apalagi selamanya.
Dmitry Grigoryev

Jawaban:

33

Saat truekeluar, sisi baca pipa ditutup, tetapi yesterus mencoba menulis ke sisi tulis. Kondisi ini disebut "pipa rusak", dan menyebabkan kernel mengirim SIGPIPEsinyal yes. Karena yestidak melakukan sesuatu yang istimewa pada sinyal ini, itu akan dibunuh. Jika mengabaikan sinyal, writepanggilannya akan gagal dengan kode kesalahan EPIPE. Program yang melakukan itu harus dipersiapkan untuk memperhatikan EPIPEdan berhenti menulis, atau mereka akan masuk ke loop yang tak terbatas.

Jika Anda melakukan strace yes | true1, Anda dapat melihat kernel mempersiapkan kedua kemungkinan:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

stracesedang menonton acara melalui API debugger, yang pertama kali memberitahukannya tentang panggilan sistem yang kembali dengan kesalahan, dan kemudian tentang sinyal. Namun dari yesperspektif, sinyal terjadi terlebih dahulu. (Secara teknis, sinyal dikirim setelah kernel mengembalikan kontrol ke ruang pengguna, tetapi sebelum instruksi mesin dijalankan lagi, sehingga fungsi write"pembungkus" di perpustakaan C tidak mendapatkan kesempatan untuk mengatur errnodan kembali ke aplikasi.)


1 Sedihnya, stracekhusus untuk Linux. Sebagian besar Unix modern memiliki beberapa perintah yang melakukan hal serupa, tetapi sering memiliki nama yang berbeda, mungkin tidak memecahkan kode syscall argumen secara menyeluruh, dan kadang-kadang hanya berfungsi untuk root.

casey
sumber
3
@hugomg dalam kasus itu, pipa itu sama sekali tidak relevan.
muru
3
@hugomg karena tidak yesada yang terhubung ke pipa.
muru
4
Memang benar, itu adalah demonstrasi perilaku yang didokumentasikan "tunggu sampai semua perintah selesai sebelum mengakhiri pipa". Itu hanya mencegah yesdari mendapatkan SIGPIPE, karena FD yang ditulisnya tidak terhubung ke pipa.
Tom Hunt
2
@hugomg, ini berulang selamanya dengan cara yang sama yaitu yes >/dev/nullberulang selamanya. Ini sama sekali tidak menunjukkan tentang pipa yang tidak juga berlaku untuk perintah sederhana (seperti perilaku menunggu untuk penghentian yang Tom tunjukkan juga berlaku untuk perintah sederhana).
Charles Duffy
2
@ zwol: Saya pikir kami menggunakan istilah kami dengan makna yang sedikit berbeda di sini, atau memikirkan hal-hal dari sudut pandang yang sedikit berbeda ... tetapi dalam kedua kasus, write()(fungsi dalam libc) tidak kembali (kontrol transfer ke PC yang mengikutinya) sampai setelah pengendali sinyal telah berjalan, tetapi karena pengendali sinyal mengakhiri program, kontrol tidak pernah ditransfer dan karenanya write()tidak pernah kembali. Ya, itu diimplementasikan di kernel dengan xxx_write()mengembalikan beberapa fungsi -EPIPE, tetapi kami sedang men-debug program ruang pengguna dan tidak tertarik dengan itu.
Dietrich Epp
5

Apakah ada kerang di mana ya | apakah benar akan berulang selamanya?

Tidak mungkin, karena yesperintahnya menggunakan pipa, dan itu akan gagal ketika pipa rusak. sleepdi sisi lain, tidak menggunakan pipa, jadi:

sleep 100000000 | true

akan berjalan setidaknya selama 10.0000000 detik.

muru
sumber
2
Hati-hati pada semua shell modern yang tidak membayar perintah builtin terakhir (paling kanan) dalam pipa dan di mana truebuiltin. Hal ini berlaku untuk versi terbaru dari Bourne Shell, ksh93, zsh. Jika Anda menekan ^Zketika perintah seperti itu berjalan, ini akan menunda tidur dan shell tidak akan pernah bisa pulih tanpa bantuan eksternal.
schily
3
zsh 4.3.4 (i386-pc-solaris2.11) di sini, jadi sepertinya ini baru-baru ini dimodifikasi. Ide yang menarik, saya perlu melihat apakah saya bisa menerapkan perbaikan yang sama untuk Bourne Shell. Masih ada pertanyaan bagaimana cara kerjanya, dan kelompok proses mana yang digunakan seperti dalam Bourne Shell fakta bahwa perintah paling tinggi adalah builtin ditemukan setelah grup proses untuk tidur sudah diatur untuk selamanya.
schily
2
@CharlesDuffy dari apa yang saya pahami, schily mengelola versi sh, yang mendukung perubahan dari shell modern. Dia mempostingnya di sini, di suatu tempat.
muru
3
Bourne Shell di arsip pusaka dipertahankan sampai ~ 2007 tetapi tidak pernah dibuat sepenuhnya portabel karena masih berisi panggilan ke sbrk(). Versi portabel dan terawat ada dalam bundel alat schily dan @Charles Duffy telah menemukan lokasi untuk informasi ;-)
schily
2
@muru banyak fitur yang saya backport ke Bourne Shell berasal dari saya bsh(Berthold Shell dari VBERTOS, versi memori virtual yang disempurnakan UNOS - klon UNIX pertama). Bsh memang mendapatkan banyak fitur csh pada tahun 1984 dan 1985 tetapi mekanisme alias dari UNOS lebih unggul daripada yang dari csh pada tahun 1980. Fitur Bourne Shell baru lainnya adalah dari POSIX, untuk memungkinkannya mendekati kepatuhan POSIX.
schily