pipa, {daftar; } hanya bekerja dengan beberapa program

13

Perlu penjelasan dari pengguna yang kuat untuk perilaku yang tidak terduga seperti itu:

ps -eF | { head -n 1;grep worker; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root       441     2  0     0     0   2 paź15 ?       00:00:00 [kworker/2:1H]

semuanya terlihat ok

ls -la / | { head -n 1;grep sbin; }

hanya menampilkan keluaran dari head

... Saya memikirkan stdout 2>&1dan tidak bekerja bagi saya itu aneh, ada penjelasan atau menyarankan cara menanganinya?

ast
sumber
1
Yang terakhir harus mencetak semuanya. The headdan grepmelakukan apa-apa di sana.
jordanm
ya kamu benar. Tetapi alih-alih, mengapa ps -eF bekerja sementara ls -la / tidak?
ast

Jawaban:

9

Saya melakukan investigasi menggunakan stracedan tampaknya karena cara program di sisi kiri pipa itu menulis ke terminal. Ketika lsperintah dieksekusi itu menulis semua data dalam satu write(). Ini menyebabkan headmengkonsumsi semua stdin.

Di sisi lain psmenulis data dalam kumpulan, jadi hanya yang pertama write()dikonsumsi oleh head, dan kemudian ada. Nanti panggilan ke write()akan pergi ke proses yang baru melahirkan grep.

Ini berarti bahwa itu tidak akan berfungsi jika proses yang Anda coba grepuntuk tidak terjadi di yang pertama write(), karena greptidak bisa melihat semua data (itu melihat bahkan kurang dari sekedar data minus baris pertama).

Berikut adalah contoh mencoba untuk mencoba pid 1 di sistem saya:

$ ps -eF | { head -n2; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | grep '/lib/systemd/systemd$'
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | { head -n1; grep '/lib/systemd/systemd$'; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD

ps -eFContoh Anda hanya berfungsi secara kebetulan.

jordanm
sumber
terima kasih banyak dan komprehensif terima kasih banyak
ast
1
Sebenarnya ini lebih merupakan kondisi balapan. Hanya saja lebih lambat melakukan beberapa write()panggilan. Jika headlambat untuk melakukan read()panggilan itu (sehingga penyangga pipa memiliki semua data di dalamnya), itu akan menunjukkan perilaku yang sama di kedua lsdan ps.
Patrick
6

Ini disebabkan oleh buffering di glibc. Dalam kasus lsoutput dalam satu buffer internal dan dengan demikian diteruskan hanya ke head. Untuk ps -eF, output lebih besar dan begitu headselesai, berikut ini grepmendapatkan bagian yang tersisa dari (tetapi tidak keseluruhan) dari output ps.

Anda dapat membuangnya dengan membatalkan penyangga pipa - misalnya dengan sed -u(Saya tidak yakin itu bukan ekstensi GNU):

$ ls -al / | sed -u "#" | { head -n 1; grep bin; }
total 76
drwxr-xr-x   2 root root  4096 Oct  2 21:52 bin
drwxr-xr-x   2 root root  8192 Oct  3 01:54 sbin
peterph
sumber
4

Apa yang terjadi adalah yang head -n 1membaca lebih dari 1 baris. Untuk throughput yang optimal, head membaca potongan byte, sehingga mungkin membaca 1024 byte sekaligus, dan kemudian mencari melalui byte tersebut untuk jeda baris pertama. Karena pemutusan garis mungkin terjadi di tengah-tengah 1024 byte itu, sisa data akan hilang. Itu tidak bisa dimasukkan kembali ke pipa. Jadi proses selanjutnya yang dijalankan hanya mendapatkan byte 1025 dan terus.

Perintah pertama Anda berhasil untuk berhasil karena kworkerprosesnya adalah setelah potongan pertama yang headberbunyi.

Agar ini berfungsi, headharus membaca 1 karakter sekaligus. Tapi ini sangat lambat, jadi tidak.
Satu-satunya cara untuk melakukan sesuatu seperti ini secara efisien adalah dengan memiliki satu proses melakukan "kepala" dan "grep".

Berikut adalah 2 cara untuk melakukan ini:

echo -e '1\n2\n3\n4\n5' | perl -ne 'print if $i++ == 0 || /4/'

atau

echo -e '1\n2\n3\n4\n5' | awk '{if (NR == 1 || /4/) print }'

Ada banyak lagi ...

Patrick
sumber
ya saya sudah tahu 'cara awk' untuk menangani tugas ini, tetapi bertanya-tanya mengapa perilaku begitu tak terduga dengan {list; }. Terima kasih telah menjelaskan cara kerjanya. Saya terkesan dengan semua jawaban di atas
ast
2

Jika Anda hanya menginginkan satu atau dua baris pertama, jenis trik berikut ini berfungsi dan menghindari masalah buffering yang disebabkan oleh penggunaan dua perintah berbeda untuk membaca aliran keluaran:

$ ps -eF   | { IFS= read -r x ; echo "$x" ; grep worker; }
$ ls -la / | { IFS= read -r x ; echo "$x" ; grep sbin; }

Ini readbuilt-in ke shell dan tidak mengkonsumsi seluruh buffer input hanya untuk output satu baris, jadi menggunakan readsemua sisa output untuk perintah berikut.

Jika Anda ingin menonjolkan masalah buffering yang ditunjukkan oleh contoh Anda yang menggunakan dua perintah berbeda, tambahkan a sleepuntuk menghilangkan masalah waktu dan izinkan perintah di sebelah kiri untuk menghasilkan semua outputnya sebelum perintah di sebelah kanan mencoba membaca salah satu Itu:

$ ps -eF   | { sleep 5 ; head -n 1 ; grep worker; }
$ ls -la / | { sleep 5 ; head -n 1 ; grep sbin; }

Sekarang, kedua contoh di atas gagal dengan cara yang sama - headmembaca seluruh buffer dari output hanya untuk menghasilkan satu baris, dan buffer itu tidak tersedia sebagai berikut grep.

Anda dapat melihat masalah buffering dengan lebih jelas dengan menggunakan beberapa contoh yang memberi nomor pada garis keluaran sehingga Anda dapat mengetahui baris mana yang hilang:

$ ps -eF          | cat -n | { sleep 5 ; head -n 1 ; head ; }
$ ls -la /usr/bin | cat -n | { sleep 5 ; head -n 1 ; head ; }

Cara sederhana untuk melihat masalah buffering adalah menggunakan seqyang menghasilkan daftar angka. Kami dapat dengan mudah mengetahui nomor mana yang hilang:

$ seq 1 100000    | { sleep 5 ; head -n 1 ; head ; }
1

1861
1862
1863
1864
1865
1866
1867
1868
1869

Solusi trik saya menggunakan shell untuk membaca dan mengulangi baris pertama bekerja dengan benar bahkan dengan penundaan tidur ditambahkan:

$ seq 1 100000 | { sleep 5 ; IFS= read -r x ; echo "$x" ; head ; }
1
2
3
4
5
6
7
8
9
10
11

Di bawah ini adalah contoh lengkap yang menunjukkan headmasalah buffering, menunjukkan bagaimana headmengkonsumsi seluruh buffer output hanya untuk menghasilkan lima barisnya setiap kali. Buffer yang dikonsumsi tidak tersedia untuk headperintah berikutnya dalam urutan:

$ seq 1 100000 | { sleep 5 ; head -5 ; head -5 ; head -5 ; head -5 ; }
1
2
3
4
5

1861
1862
1863
1864
499
3500
3501
3502
3503
7
5138
5139
5140
5141

Melihat angka di 1861atas, kita dapat menghitung ukuran buffer yang digunakan headdengan menghitung seqoutput dari 1ke 1860:

$ seq 1 1860 | wc -c
8193

Kita melihat bahwa headbuffering dengan membaca 8KB penuh (8 * 1024 byte) dari output pipa pada suatu waktu, bahkan untuk menghasilkan hanya beberapa baris output sendiri.

Ian D. Allen
sumber