grep tidak menghasilkan sampai EOF jika disalurkan melalui cat

19

Diberikan contoh minimal ini

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; )

itu menghasilkan LINE 1dan kemudian, setelah satu detik, menghasilkan LINE 2, seperti yang diharapkan .


Jika kita menyalurkan ini ke grep LINE

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE

perilakunya sama seperti dalam kasus sebelumnya, seperti yang diharapkan .


Jika, sebagai alternatif, kami pipa ini ke cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | cat

sekali lagi tingkah lakunya sama, seperti yang diharapkan .


Namun , jika kita menyalurkan ke grep LINE, dan kemudian ke cat,

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE | cat

tidak ada output sampai satu detik berlalu, dan kedua garis segera muncul pada output, yang tidak saya harapkan .


Mengapa ini terjadi dan bagaimana saya bisa membuat versi terakhir untuk berperilaku dengan cara yang sama seperti tiga perintah pertama?

lisyarus
sumber
catmenggabungkan file. Apa yang ingin Anda lakukan dengan menyalurkan cat?
Douglas Held
15
@DouglasHeld Ketika dipanggil tanpa argumen, catcukup membaca stdindan menghasilkan stdout. Tentu saja, saya datang dengan pertanyaan ini dengan banyak hal rumit di tempat echodan cat, tetapi ini ternyata tidak relevan, karena masalah muncul dengan contoh yang jauh lebih sederhana.
lisyarus
3
@DouglasHeld: Perpipaan ke kucing sering berguna untuk memaksa stdout agar tidak menjadi terminal. Sebagai contoh, ini adalah cara mudah untuk mendapatkan banyak perintah untuk tidak menggunakan keluaran berwarna.
wchargin
Saya bersumpah ini adalah duplikat dari pertanyaan lain tentang Stack Overflow!
iBug
@wargargin terima kasih banyak, Anda telah mengajari saya sesuatu yang baru tentang posix yang tidak pernah saya ketahui.
Douglas Diadakan

Jawaban:

38

Ketika (setidaknya GNU) grepoutput bukan terminal, itu output buffer, yang menyebabkan perilaku yang Anda lihat. Anda dapat menonaktifkan ini baik menggunakan GNU grep's --line-bufferedpilihan:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep --line-buffered LINE | cat

atau stdbufutilitas:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | stdbuf -oL grep LINE | cat

Matikan buffering in pipe memiliki lebih banyak tentang topik ini.

Stephen Kitt
sumber
26

Penjelasan yang disederhanakan

Seperti banyak utilitas, ini bukan sesuatu yang khas untuk satu program, grepbervariasi keluaran standar antara menjadi buffer garis dan buffer penuh . Dalam kasus sebelumnya, pustaka C buffer menghasilkan data dalam memori sampai buffer yang menahan data tersebut diisi atau karakter linefeed ditambahkan ke dalamnya (atau program berakhir dengan bersih), di mana ia dipanggil write()untuk benar-benar menulis konten buffer. Dalam kasus terakhir, hanya buffer dalam memori yang penuh (atau program berakhir dengan bersih) yang memicu write().

Penjelasan lebih rinci

Ini adalah penjelasan yang terkenal, tapi sedikit salah. Bahkan, output standar tidak buffered garis tetapi buffered pintar di perpustakaan GNU C dan perpustakaan BSD C. Output standar adalah juga memerah ketika membaca standar masukan kehabisan nya penyangga di memori (input pre-read) dan C perpustakaan harus memanggil read()untuk mengambil beberapa masukan lebih dan itu adalah membaca awal baris baru. (Salah satu alasan untuk ini adalah untuk mencegah kebuntuan ketika program lain menghubungkan dirinya sendiri ke kedua ujung filter dan berharap untuk dapat mengoperasikan baris-demi-baris, bergantian antara menulis ke filter dan membaca dari itu, seperti "proses-proses" di GNU awksebagai contoh.)

Pengaruh perpustakaan C

grepdan utilitas lain melakukan ini - atau, lebih tepatnya, pustaka C yang mereka gunakan melakukan ini, karena ini adalah fitur pemrograman yang didefinisikan dalam bahasa C - berdasarkan pada apa yang mereka deteksi sebagai keluaran standar. Jika (dan hanya jika) itu bukan perangkat interaktif, mereka memilih buffering penuh, atau mereka memilih buffering pintar. Sebuah pipa dianggap bukan perangkat interaktif, karena definisi menjadi perangkat interaktif, setidaknya di dunia Unix dan Linux, pada dasarnya adalah isatty()panggilan yang mengembalikan true untuk deskriptor file yang relevan.

Penanganan untuk menonaktifkan buffering penuh

Beberapa utilitas seperti grepmemiliki opsi istimewa seperti --line-buffereditu mengubah keputusan ini, yang seperti yang Anda lihat salah nama. Tetapi sebagian kecil dari program filter yang dapat digunakan sebenarnya memiliki opsi seperti itu.

Lebih umum, seseorang dapat menggunakan alat yang menggali internal tertentu dari perpustakaan C dan mengubah pengambilan keputusannya (yang memiliki masalah keamanan jika program yang akan diubah diatur-UID, dan juga khusus untuk perpustakaan C tertentu, dan memang khusus untuk program yang ditulis dalam atau berlapis di atas bahasa C), atau alat seperti ptybandageitu tidak mengubah internal program tetapi hanya menempatkan terminal pseudo sebagai output standar sehingga keputusan keluar sebagai "interaktif", untuk mempengaruhi ini.

Bacaan lebih lanjut

JdeBP
sumber
1
Jika frasa "baris buffered" adalah nama yang salah, maka itu bukan kesalahan grep, tetapi dari panggilan pustaka yang mendasarinya, setbuf/setvbuf . Saya tidak tahu tentang referensi online yang dapat diandalkan untuk standar C, tetapi misalnya halaman manual Linux dan FreeBSD bersama dengan deskripsi POSIX setvbufmenyebutnya "line buffered". Bahkan konstanta simbolis untuk itu adalah _IOLBF.
ilkkachu
Nah sekarang Anda sudah belajar lebih baik. Strategi buffering ini dijelaskan dalam dokumen perpustakaan GNU C, meskipun secara singkat. Laurent Bercot lebih jujur ​​tentang masalah ini. Saya telah menyebutkannya juga.
JdeBP
Saya tidak berpikir "Harapan Anda salah" adalah judul yang bagus untuk penjelasan luar biasa tentang buffering keluaran ini. Saya harap Anda tidak keberatan bahwa saya menghapusnya dan menambahkan beberapa judul deskriptif untuk setiap bagian dari jawabannya.
Anthony G - keadilan untuk Monica
2
@ilkkachu Standar C memang menggunakan "line buffered". Per 7.21.3 File , paragraf 3 : "Ketika streaming tidak disatukan, ... Ketika streaming sepenuhnya buffered, ... Ketika aliran buffered garis, karakter dimaksudkan untuk dikirim ke atau dari lingkungan host sebagai blok ketika karakter baris baru ditemukan ... "Sebenarnya, Standar C menggunakan frasa persis" buffer line "lima kali. Jadi itu bukan istilah yang salah.
Andrew Henle
1
Selanjutnya, pendekatan yang digambarkan di sini sebagai "buffering pintar", seperti yang saya mengerti, tampaknya hanya apa yang standar C menggambarkan sebagai "line buffering". Secara khusus, selain menyiram buffer di baris baru, "Ketika aliran buffered garis, karakter dimaksudkan untuk ditransmisikan ke atau dari lingkungan host sebagai blok ketika [...] input diminta pada aliran unbuffered, atau ketika input diminta pada aliran buffered baris yang memerlukan transmisi karakter dari lingkungan host. " Jadi ini bukan kekhasan GNU atau BSD, melainkan apa yang dibutuhkan oleh bahasa.
John Bollinger
7

Menggunakan

grep --line-buffered

untuk membuat grep tidak buffer lebih dari satu baris sekaligus.

choroba
sumber