Pipa, bagaimana aliran data dalam pipa?

22

Saya tidak mengerti bagaimana data mengalir dalam pipa dan berharap seseorang dapat mengklarifikasi apa yang sedang terjadi di sana.

Saya pikir pipa perintah memproses file (teks, array string) sejalan dengan cara baris. (Jika setiap perintah itu sendiri bekerja baris demi baris.) Setiap baris teks melewati pipeline, perintah jangan menunggu sebelumnya untuk menyelesaikan pemrosesan seluruh input.

Tapi sepertinya tidak demikian.

Ini adalah contoh uji. Ada beberapa baris teks. Saya huruf besar dan ulangi setiap baris dua kali. Saya melakukannya dengan cat text | tr '[:lower:]' '[:upper:]' | sed 'p'.

Untuk mengikuti proses ini, kita dapat menjalankannya "secara interaktif" - lewati nama file input cat. Setiap bagian dari pipa berjalan baris demi baris:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Tetapi pipeline lengkap menunggu saya untuk menyelesaikan input dengan EOFdan hanya kemudian mencetak hasilnya:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

Apakah seharusnya begitu? Mengapa tidak baris demi baris?

xealits
sumber
Ini bukan pipa, itu catbuffering sampai stdin ditutup.
goldilocks
tapi trdan sedlakukan garis proses dari catsebelum stdin ditutup
xealits
Default yang digunakan oleh stdio (yang saya percaya semua program yang digunakan) adalah stderr tidak dibuat-buat, dan stdout adalah buffer line saat menulis ke terminal dan buffer penuh sebaliknya (misalnya jika menulis ke file atau pipa) . Beberapa perintah memiliki flag yang dapat mengubah buffering stdout, tetapi sepertinya tr tidak.
kasperd

Jawaban:

36

Ada aturan buffering umum yang diikuti oleh pustaka I / O standar C ( stdio) yang digunakan sebagian besar program unix. Jika output pergi ke terminal, itu memerah pada akhir setiap baris; jika tidak maka akan memerah hanya ketika buffer (8K pada sistem Linux / amd64 saya; bisa berbeda pada Anda) penuh.

Jika semua utilitas Anda mengikuti aturan umum, Anda akan melihat output tertunda dalam semua contoh Anda ( cat|sed, cat|tr, dan cat|tr|sed). Tapi ada pengecualian: GNU cattidak pernah mendukung outputnya. Entah itu tidak menggunakan stdioatau mengubah stdiokebijakan buffering default .

Saya bisa yakin Anda menggunakan GNU catdan bukan unix lain catkarena yang lain tidak akan berperilaku seperti ini. Unix tradisional catmemiliki -uopsi untuk meminta keluaran tanpa buffer. GNU catmengabaikan -uopsi karena outputnya selalu tidak dibuat-buat.

Jadi, setiap kali Anda memiliki pipa dengan catdi sebelah kiri, dalam sistem GNU, bagian data melalui pipa tidak akan tertunda. The catbahkan tidak akan baris demi baris - terminal Anda melakukan hal itu. Saat Anda mengetikkan input untuk cat, terminal Anda berada dalam mode "kanonik" - berbasis garis, dengan tombol pengeditan seperti backspace dan ctrl-U menawarkan Anda kesempatan untuk mengedit baris yang telah Anda ketikkan sebelum mengirimkannya Enter.

Dalam cat|tr|sedcontoh, trmasih menerima data dari catsegera setelah Anda menekan Enter, tetapi trmengikuti stdiokebijakan default: outputnya adalah pipa, sehingga tidak memerah setelah setiap baris. Itu menulis ke pipa kedua ketika buffer penuh, atau ketika EOF diterima, mana yang lebih dulu.

sedjuga mengikuti stdiokebijakan default, tetapi outputnya pergi ke terminal sehingga akan menulis setiap baris segera setelah selesai dengannya. Ini memiliki efek pada berapa banyak Anda harus mengetik sebelum sesuatu muncul di ujung pipa - jika sedsedang memblokir-buffer outputnya, Anda harus mengetik dua kali lebih banyak (untuk mengisi trbuffer output dan sed output penyangga).

GNU sedmemiliki -uopsi jadi jika Anda membalik urutan dan menggunakan cat|sed -u|trAnda akan melihat output muncul kembali secara instan. ( sed -uPilihannya mungkin tersedia di tempat lain tetapi saya tidak berpikir itu tradisi unix kuno seperti cat -u) Sejauh yang saya tahu tidak ada opsi yang setara untuk itu tr.

Ada utilitas yang disebut stdbufyang memungkinkan Anda mengubah mode buffering dari setiap perintah yang menggunakan stdiodefault. Agak rapuh karena digunakan LD_PRELOADuntuk mencapai sesuatu yang tidak didukung oleh pustaka C, tetapi dalam kasus ini tampaknya berfungsi:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

sumber
1
Terima kasih! Jawaban yang luar biasa. Mungkin saya harus menyebutkan buffering dalam pertanyaan dengan cara tertentu, sehingga orang dapat menemukannya.
xealits
teedan ddjuga biasanya bermain dengan aturannya sendiri. Ketika dikombinasikan secara imajinatif, ketiga alat ini dapat dengan mudah meniadakan kebutuhan apa pun stdbufdalam pipa latar belakang.
mikeserv
1
Ini adalah salah satu alasan untuk menghindari penggunaan kucing yang tidak berguna .
hobbs
8

Ini benar-benar membuat saya berpikir untuk mengerti dan bahkan lebih untuk menjawab. Pertanyaan bagus (saya akan menjawabnya selanjutnya).

Anda lalai untuk mencoba tr | seditem debug Anda di atas:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Jadi ternyata trpenyangga. Pelajari sesuatu yang baru setiap hari!

EDIT :

Saat saya memikirkan hal ini, kami telah mengisolasi penyebabnya, tetapi tidak memberikan penjelasan. Jika Anda cat | tr, itu menulis segera, jika Anda cat | sed, itu menulis segera, tetapi jika Anda tr | sed, itu menunggu untuk EOF. Saya akan menyarankan jawabannya mungkin dimakamkan tratau sedkode sumber, dan bukan masalah pipa.

EDIT :

Saya melihat Wumpus memberikan penjelasan saat saya mengetik edit terakhir. Terima kasih!

Poisson Aerohead
sumber
1
memang mereka menjadi penyangga! dan tes dengan garis kira-kira 8kb, seperti yang disebutkan Wumpus, menunjukkan buffer memang 8Kb. Saya ingin menerima kedua jawaban untuk berbagi reputasi, tetapi saya akan menganggap jawaban Wumpus lebih lengkap. Bagaimanapun, terima kasih!
xealits
1
Tidak masalah, jawabanku adalah jawaban empiris, jawabannya yang luas.
Poisson Aerohead
Lihat juga pertanyaan ini yang menunjukkan cara menggunakan stdbufyang mungkin juga bermanfaat. unix.stackexchange.com/questions/182537/...
Joe