Mengapa 'sed q' bekerja secara berbeda saat membaca dari sebuah pipa?

25

Saya membuat file uji bernama 'test' yang berisi yang berikut:

xxx
yyy
zzz

Saya menjalankan perintah:

(sed '/y/ q'; echo aaa; cat) < test

dan saya mendapat:

xxx
yyy
aaa
zzz

Lalu aku berlari:

cat test | (sed '/y/ q'; echo aaa; cat)

dan mendapatkan:

xxx
yyy
aaa

Pertanyaan

sedmembaca dan mencetak sampai bertemu garis dengan 'y', lalu berhenti. Dalam kasus pertama, tetapi bukan yang kedua, kucing membaca dan mencetak sisanya.

Dapatkah seseorang menjelaskan fenomena apa di balik perbedaan perilaku ini?

Saya juga memperhatikan cara kerjanya di Ubuntu 16.04 dan Centos 6 tetapi di Centos 7 tidak ada perintah yang mencetak 'zzz'.

Antti Kuusela
sumber
Dugaan saya adalah bahwa cat(di sub shell) dapat menggunakan kembali deskriptor file dalam kasus pertama, karena stdin terikat ke file nyata. Dalam kasus kedua, stdin berasal dari sebuah pipa dan bukan file asli. Perhatikan bahwa juga (sed '/y/ q'; echo aaa; cat) < <(cat test)tidak mencetak zzz.
Martin Nyolt
1
Contoh yang lebih sederhana: (head -n1; head -n1) < testdancat test | (head -n1; head -n1)
Martin Nyolt

Jawaban:

22

Ketika file input dapat dicari (seperti membaca dari file biasa) atau tidak dapat dicari (seperti membaca dari sebuah pipa), sed(dan utilitas standar lainnya) akan berperilaku berbeda (Baca INPUT FILESbagian dalam tautan ini ).

Kutipan dari dokter:

Ketika sebuah utilitas standar membaca file input yang dapat dicari dan berakhir tanpa kesalahan sebelum mencapai file-akhir, utilitas harus memastikan bahwa offset file dalam deskripsi file terbuka diposisikan dengan benar melewati byte terakhir yang diproses oleh utilitas.

Jadi di:

(sed '/y/ q'; echo aaa; cat) < test

sedmelakukan qperintah uit sebelum mencapai EOF, sehingga file diimbangi pada awal zzzbaris, sehingga catdapat terus mencetak baris yang tersisa (GNU tidak mematuhi POSIX dalam kondisi tertentu, lihat di bawah).

Dan melanjutkan dari dokumen:

Untuk file yang tidak dapat dicari, keadaan file offset dalam deskripsi file terbuka untuk file itu tidak ditentukan

Dalam hal ini, perilaku tidak ditentukan. Sebagian besar alat standar, termasuk sedakan mengkonsumsi input sebanyak mungkin. Bunyinya melewati yyybaris, dan quit tanpa mengembalikan file offset, sehingga tidak ada yang tersisa untuk cat.


GNU sedtidak sesuai dengan standar, tergantung pada implementasi sistem stdio dan versi glibc:

$ (gsed '/y/ q'; echo aaa; cat) < test
xxx
yyy
aaa

Di sini, hasilnya didapat dari Mac OSX 10.11.6, mesin virtual Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19, yang dijalankan pada Openstack dengan backend CEPH.

Pada sistem tersebut, Anda dapat menggunakan -uopsi untuk mencapai perilaku standar:

(gsed -u '/y/ q'; echo aaa; cat) </tmp/test

dan untuk pipa:

$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz

yang mengarah pada kinerja yang sangat tidak efisien, karena sedharus membaca satu byte pada satu waktu. Output parsial dari strace:

$ strace -fe read sh -c '{ sed -u "/y/q"; echo aaa; cat; } <test'
...
[pid  5248] read(3, "", 4096)           = 0
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
xxx
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
yyy
...
cuonglm
sumber
1
Untuk GNU sed, itu tergantung pada implementasi stdio sistem. Pada sistem GNU (dengan libc GNU), GNU sedakan patuh karena exit()akan mencari kembali untuk file yang dikelola oleh stdio.
Stéphane Chazelas
@ StéphaneChazelas: Bagaimana cara memverifikasi itu? Dengan Centos 7.2 saya, Ubuntu 14.04 VM, sedtidak sesuai, laptop manjaro saya memiliki, semua memiliki sed versi yang sama 4.2.2
cuonglm
@ StéphaneChazelas: Kedengarannya seperti sesuatu terjadi di bawah tenda. Di mesin virtual saya, strace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'tunjukkan bahwa tidak ada lseek()yang dilakukan, sementara di manjaro saya lseek()dipanggil sebelumnya exit_group().
cuonglm
Saya kira itu ke versi libc GNU. Anda dapat menguji dengan suatu main() { char buf[999]; gets(buf); }'program.
Stéphane Chazelas
1
@ StéphaneChazelas: Dikonfirmasi. Kedua VM saya memiliki 2,17 dan 2,19, sedangkan yang ada di manjaro saya adalah 2,23. Apakah ini dianggap sebagai bug glibc? Apakah Anda memiliki informasi tentang perubahan antara versi glibc
cuonglm