grep không xuất ra cho đến khi EOF nếu được dẫn qua cat


19

Cho ví dụ tối thiểu này

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

nó xuất ra LINE 1và sau đó, sau một giây, đầu ra LINE 2, như mong đợi .


Nếu chúng ta đặt cái này vào grep LINE

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

hành vi giống như trong trường hợp trước, như mong đợi .


Nếu, thay vào đó, chúng tôi dẫn đường này đến cat

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

hành vi lại giống nhau, như mong đợi .


Tuy nhiên , nếu chúng ta dẫn đến grep LINE, và sau đó cat,

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

không có đầu ra cho đến khi một giây trôi qua và cả hai dòng xuất hiện trên đầu ra ngay lập tức, điều mà tôi không mong đợi .


Tại sao điều này xảy ra và làm thế nào tôi có thể làm cho phiên bản cuối cùng hoạt động giống như ba lệnh đầu tiên?


catnối các tập tin. Bạn đang cố gắng làm gì bằng cách dẫn vào cat?
Douglas Held

15
@DoumundHeld Khi được gọi mà không có đối số, catchỉ cần đọc stdinvà xuất ra stdout. Tất nhiên, tôi đã đưa ra câu hỏi này với rất nhiều thứ phức tạp thay thế echocat, nhưng những thứ này hóa ra không liên quan, vì vấn đề xuất hiện với các ví dụ đơn giản hơn nhiều.
lisyarus

3
@DoumundHeld: Đường ống cho mèo thường hữu ích để buộc thiết bị xuất chuẩn không phải là thiết bị đầu cuối. Ví dụ, đây là một cách dễ dàng để có được nhiều lệnh không sử dụng đầu ra được tô màu.
wchargein

Tôi thề đây là một bản sao của một câu hỏi khác trên Stack Overflow!
iBug

@wchargein cảm ơn bạn rất nhiều, bạn đã dạy cho tôi một vài điều mới về posix mà tôi chưa từng biết.
Douglas Held

Câu trả lời:


38

Khi đầu ra của (ít nhất là GNU) grepkhông phải là một thiết bị đầu cuối, nó sẽ đệm đầu ra của nó, đó là nguyên nhân gây ra hành vi mà bạn nhìn thấy. Bạn có thể tắt tùy chọn này bằng tùy chọn grepcủa GNU --line-buffered:

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

hoặc stdbuftiện ích:

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

Tắt bộ đệm trong đường ống có nhiều hơn về chủ đề này.


26

Giải thích đơn giản

Giống như nhiều tiện ích, đây không phải là một thứ gì đó đặc biệt với một chương trình, grepthay đổi đầu ra tiêu chuẩn của nó giữa được đệm dòngđược đệm hoàn toàn . Trong trường hợp trước, thư viện C đệm dữ liệu đầu ra trong bộ nhớ cho đến khi bộ đệm chứa các dữ liệu đó được điền hoặc ký tự linefeed được thêm vào nó (hoặc chương trình kết thúc sạch), sau đó nó gọi write()để thực sự ghi nội dung bộ đệm. Trong trường hợp sau, chỉ có bộ đệm trong bộ nhớ trở nên đầy (hoặc chương trình kết thúc sạch) mới kích hoạt write().

Giải thích chi tiết hơn

Đây là lời giải thích nổi tiếng, nhưng hơi sai. Trong thực tế, đầu ra tiêu chuẩn không phải là bộ đệm dòng mà là bộ đệm thông minh trong thư viện GNU C và thư viện BSD C. Chuẩn đầu ra là cũng đỏ ửng khi đọc tiêu chuẩn đầu vào làm cạn kiệt của nó đệm trong bộ nhớ (đầu vào trước khi đọc) và thư viện C có gọi read()để lấy một số đầu vào nhiều hơn nó được đọc đầu một dòng mới. (Một lý do cho việc này là để ngăn chặn sự bế tắc khi một chương trình khác tự kết nối với cả hai đầu của bộ lọc và hy vọng có thể vận hành từng dòng một, xen kẽ giữa việc ghi vào bộ lọc và đọc từ nó; như "coprocesses" trong GNU awkví dụ.)

Ảnh hưởng thư viện C

grepvà các tiện ích khác thực hiện việc này - hoặc, đúng hơn, các thư viện C mà họ sử dụng thực hiện việc này, bởi vì đây là một tính năng được xác định của lập trình bằng ngôn ngữ C - dựa trên những gì họ phát hiện ra đầu ra tiêu chuẩn của họ. Nếu (và chỉ nếu) nó không phải là một thiết bị tương tác, họ chọn bộ đệm đầy đủ, nếu không họ chọn bộ đệm thông minh. Một đường ống được coi là không phải là một thiết bị tương tác, bởi vì định nghĩa là một thiết bị tương tác, ít nhất là trong thế giới của Unix và Linux, về cơ bản là isatty()cuộc gọi trả về đúng cho bộ mô tả tệp có liên quan.

Giải pháp để vô hiệu hóa bộ đệm đầy đủ

Một số tiện ích như grepcó các tùy chọn idiosyncratic như --line-bufferedthay đổi quyết định này, mà bạn có thể thấy là đặt tên sai. Nhưng một phần nhỏ của các chương trình lọc mà người ta có thể sử dụng thực sự có một tùy chọn như vậy.

Tổng quát hơn, người ta có thể sử dụng các công cụ đào sâu vào các phần bên trong cụ thể của thư viện C và thay đổi việc ra quyết định (có vấn đề về bảo mật nếu chương trình bị thay đổi là set-UID và cũng cụ thể cho các thư viện C cụ thể, và thực tế là cụ thể đối với các chương trình viết bằng hoặc lớp trên đầu trang của ngôn ngữ C), hoặc các công cụ như ptybandagerằng không thay đổi bên trong của chương trình mà chỉ đơn giản xen một pseudo-thiết bị đầu cuối như đầu ra tiêu chuẩn để quyết định đi ra là "tương tác", để ảnh hưởng đến điều này.

đọc thêm


1
Nếu cụm từ "dòng đệm" là một cách gọi sai, thì đó không thực sự là lỗi của grep, mà là của các cuộc gọi thư viện cơ bản, setbuf/setvbuf . Tôi không biết về một tài liệu tham khảo trực tuyến đáng tin cậy cho tiêu chuẩn C, nhưng ví dụ: các trang người dùng Linux và FreeBSD cùng với mô tả POSIX setvbufgọi nó là "bộ đệm dòng". Ngay cả hằng số biểu tượng cho nó là _IOLBF.
ilkkachu

Giờ bạn đã học tốt hơn. Chiến lược đệm này được mô tả trong tài liệu thư viện GNU C, mặc dù ngắn gọn. Laurent Bercot thẳng thắn hơn về vấn đề này. Tôi đã đề cập đến nó quá.
JdeBP

Tôi đã không nghĩ rằng kỳ vọng của bạn là sai, là một tiêu đề tốt cho giải thích tuyệt vời này về bộ đệm đầu ra. Tôi hy vọng bạn không bận tâm rằng tôi đã xóa nó và thêm một số tiêu đề mô tả cho mỗi phần của câu trả lời.
Anthony G - công lý cho Monica

2
@ilkkachu Tiêu chuẩn C thực sự sử dụng "bộ đệm dòng". Mỗi 7.21.3 Tệp , đoạn 3 : "Khi một luồng không được đệm, ... Khi một luồng được đệm hoàn toàn, ... Khi một luồng được đệm dòng, các ký tự được dự định truyền đến hoặc từ môi trường máy chủ như một chặn khi gặp phải một ký tự dòng mới. ... "Trên thực tế, Tiêu chuẩn C sử dụng cụm từ chính xác" dòng đệm "năm lần. Vì vậy, nó không phải là một cách hiểu sai.
Andrew Henle

1
Hơn nữa, cách tiếp cận được mô tả ở đây là "bộ đệm thông minh", theo tôi hiểu, dường như chỉ là những gì mà tiêu chuẩn C mô tả là "bộ đệm dòng". Cụ thể, ngoài việc xóa bộ đệm ở dòng mới, "Khi một luồng được đệm dòng, các ký tự được dự định được truyền đến hoặc từ môi trường máy chủ dưới dạng một khối khi yêu cầu đầu vào [...] trên luồng không có bộ đệm hoặc khi đầu vào được yêu cầu trên luồng đệm được yêu cầu truyền các ký tự từ môi trường máy chủ. " Vì vậy, đây không phải là một sự giải quyết GNU hoặc BSD, mà là những gì ngôn ngữ yêu cầu.
John Bollinger

7

Sử dụng

grep --line-buffered

để làm cho grep không đệm nhiều hơn một dòng cùng một lúc.

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.