Tại sao 'sed q' hoạt động khác nhau khi đọc từ một đường ống?


25

Tôi đã tạo một tệp thử nghiệm có tên 'test' có chứa các mục sau:

xxx
yyy
zzz

Tôi chạy lệnh:

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

và tôi đã nhận được:

xxx
yyy
aaa
zzz

Rồi tôi chạy:

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

và có:

xxx
yyy
aaa

Câu hỏi

sedđọc và in cho đến khi nó gặp một dòng với 'y', sau đó dừng lại. Trong trường hợp đầu tiên, nhưng không phải lần thứ hai, mèo đọc và in phần còn lại.

Ai đó có thể giải thích hiện tượng nào đằng sau sự khác biệt trong hành vi này?

Tôi cũng nhận thấy nó hoạt động theo cách này trong Ubuntu 16.04 và Centos 6 nhưng trong Centos 7 không có lệnh in 'zzz'.


Tôi đoán là cat(trong vỏ phụ) có thể sử dụng lại bộ mô tả tệp trong trường hợp đầu tiên, vì stdin bị ràng buộc với một tệp thực. Trong trường hợp thứ hai, stdin là từ một đường ống chứ không phải là một tập tin thực sự. Lưu ý rằng cũng (sed '/y/ q'; echo aaa; cat) < <(cat test)không in zzz.
Martin Nyolt

1
Một ví dụ đơn giản hơn: (head -n1; head -n1) < testcat test | (head -n1; head -n1)
Martin Nyolt

Câu trả lời:


22

Khi tập tin đầu vào là seekable (như đọc từ tập tin thường xuyên) hoặc un-seekable (như đọc từ một đường ống), sed(và các tiện ích tiêu chuẩn khác) sẽ hành xử khác nhau (Đọc INPUT FILESphần trong liên kết này ).

Trích dẫn từ tài liệu:

Khi một tiện ích tiêu chuẩn đọc tệp đầu vào có thể tìm kiếm và chấm dứt mà không có lỗi trước khi đến cuối tệp, tiện ích sẽ đảm bảo rằng tệp bù trong mô tả tệp mở được đặt đúng vị trí vừa qua byte cuối được xử lý bởi tiện ích.

Vì vậy, trong:

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

sedđã thực hiện qlệnh uit trước khi đạt EOF, do đó, nó để lại tập tin bù vào đầu zzzdòng, vì vậy catcó thể tiếp tục in các dòng còn lại (GNU sed không tuân thủ POSIX trong một số điều kiện, xem bên dưới).

Và tiếp tục từ tài liệu:

Đối với các tệp không thể tìm kiếm, trạng thái của tệp bù trong mô tả tệp mở cho tệp đó là không xác định

Trong trường hợp này, hành vi là không xác định. Hầu hết các công cụ tiêu chuẩn, bao gồm sedsẽ tiêu thụ đầu vào càng nhiều càng tốt. Nó đọc vượt qua yyydòng và quit mà không khôi phục lại tập tin bù đắp, vì vậy không còn gì cho cat.


GNU sedkhông tuân thủ tiêu chuẩn, phụ thuộc vào việc triển khai stdio của hệ thống và phiên bản glibc:

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

Tại đây, kết quả đã nhận được từ Mac OSX 10.11.6, máy ảo Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19, được chạy trên Openstack với phụ trợ CEPH.

Trên các hệ thống đó, bạn có thể sử dụng -utùy chọn để đạt được hành vi tiêu chuẩn:

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

và cho đường ống:

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

dẫn đến hiệu suất cực kỳ kém hiệu quả, bởi vì sedphải đọc từng byte một. Một phần đầu ra từ 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
...

1
Đối với GNU sed, điều đó phụ thuộc vào việc triển khai stdio của hệ thống. Trên các hệ thống GNU (với GNU libc), GNU sedsẽ được tuân thủ vì exit()sẽ trả lại các tệp được quản lý bởi stdio.
Stéphane Chazelas

@ StéphaneChazelas: Làm thế nào để xác minh nó? Với máy ảo Centos 7.2, Ubuntu 14.04 sedcủa tôi, không tuân thủ, máy tính xách tay manjaro của tôi cũng vậy, tất cả đều có cùng sed phiên bản 4.2.2
cuonglm

@ StéphaneChazelas: Âm thanh như có gì đó xảy ra dưới mui xe. Trên các máy ảo của tôi, strace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'cho thấy rằng không có gì lseek()được thực hiện, trong khi trong manjaro của tôi, một cái lseek()được gọi trước đó exit_group().
cuonglm

Tôi cho rằng đó là phiên bản của libc GNU. Bạn có thể kiểm tra với một main() { char buf[999]; gets(buf); }'chương trình.
Stéphane Chazelas

1
@ StéphaneChazelas: Khẳng định. Cả hai máy ảo của tôi có 2,17 và 2,19, trong khi máy ảo của tôi là 2,23. Đây có phải là một lỗi glibc? Bạn có bất kỳ thông tin nào về sự thay đổi giữa các phiên bản glibc
cuonglm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.