Tắt đệm trong đường ống


395

Tôi có một đoạn script gọi hai lệnh:

long_running_command | print_progress

Bản long_running_commandin tiến bộ nhưng tôi không hài lòng với nó. Tôi đang sử dụng print_progressđể làm cho nó đẹp hơn (cụ thể là, tôi in tiến trình trong một dòng duy nhất).

Vấn đề: Kết nối một đường ống với thiết bị xuất chuẩn cũng kích hoạt bộ đệm 4K, để chương trình in đẹp không có gì ... không có gì ... không có gì ... cả ...

Làm cách nào tôi có thể tắt bộ đệm 4K cho long_running_command(không, tôi không có nguồn)?


1
Vì vậy, khi bạn chạy long_rasty_command mà không có đường ống, bạn có thể thấy các bản cập nhật tiến độ đúng cách, nhưng khi đường ống chúng có được đệm không?

1
Vâng, đó chính xác là những gì xảy ra.
Aaron Digulla

20
Không có khả năng cho một cách đơn giản để kiểm soát bộ đệm đã là một vấn đề trong nhiều thập kỷ. Ví dụ: xem: marc.info/?l=glibc-orms&m=98313957306297&w=4 trong đó nói một cách cơ bản "Tôi không thể bị làm điều này và đây là một cái bẫy vỗ tay để biện minh cho vị trí của tôi"


1
Nó thực sự là stdio không phải là đường ống gây ra sự chậm trễ trong khi chờ đủ dữ liệu. Các đường ống có khả năng, nhưng ngay khi có bất kỳ dữ liệu nào được ghi vào đường ống, nó ngay lập tức sẵn sàng để đọc ở đầu kia.
Sam Watkins

Câu trả lời:


254

Bạn có thể sử dụng unbufferlệnh (đi kèm như một phần của expectgói), vd

unbuffer long_running_command | print_progress

unbufferkết nối với long_running_commandthông qua một giả (pty), làm cho hệ thống coi nó như một quá trình tương tác, do đó không sử dụng bộ đệm 4 kiB trong đường ống có thể là nguyên nhân của sự chậm trễ.

Đối với các đường ống dài hơn, bạn có thể phải hủy kết nối từng lệnh (ngoại trừ lệnh cuối cùng), vd

unbuffer x | unbuffer -p y | z

3
Trong thực tế, việc sử dụng một pty để kết nối với các quá trình tương tác là đúng với mong đợi nói chung.

15
Khi pipelining gọi tới unbuffer, bạn nên sử dụng đối số -p để unbuffer đọc từ stdin.

26
Lưu ý: Trên các hệ thống debian, cái này được gọi expect_unbuffervà nằm trong expect-devgói, không phải expectgói
bdonlan

4
@bdonlan: Ít nhất là trên Ubuntu (dựa trên debian), expect-devcung cấp cả hai unbufferexpect_unbuffer(cái trước là một liên kết tượng trưng cho cái sau). Các liên kết có sẵn kể từ expect 5.44.1.14-1(2009).
jfs

1
Lưu ý: Trên các hệ thống Ubuntu 14.04.x, nó cũng nằm trong gói mong đợi.
Alexandre Mazel

462

Một cách khác để lột da con mèo này là sử dụng stdbufchương trình, một phần của GNU Coreutils (FreeBSD cũng có một chương trình riêng).

stdbuf -i0 -o0 -e0 command

Điều này tắt hoàn toàn bộ đệm cho đầu vào, đầu ra và lỗi. Đối với một số ứng dụng, bộ đệm dòng có thể phù hợp hơn vì lý do hiệu suất:

stdbuf -oL -eL command

Lưu ý rằng nó chỉ hoạt động để stdiođệm ( printf(), fputs()...) cho các ứng dụng được liên kết động và chỉ khi ứng dụng đó không tự điều chỉnh bộ đệm của các luồng tiêu chuẩn của nó, mặc dù điều đó sẽ bao trùm hầu hết các ứng dụng.


6
"unbuffer" cần được cài đặt trong Ubuntu, nằm trong gói: kỳ vọng là 2MB ...
lepe

2
Điều này hoạt động tuyệt vời trên cài đặt raspbian mặc định để ghi nhật ký unbuffer. Tôi đã tìm thấy sudo stdbuff … commandcông việc mặc dù stdbuff … sudo commandkhông.
natevw

20
@qdii stdbufkhông hoạt động với tee, vì teeghi đè mặc định được đặt bởi stdbuf. Xem trang hướng dẫn của stdbuf.
ceving

5
@lepe Thật kỳ lạ, unbuffer có sự phụ thuộc vào x11 và tcl / tk, có nghĩa là nó thực sự cần> 80 MB nếu bạn cài đặt nó trên máy chủ mà không có chúng.
jpatokal

10
@qdii stdbufsử dụng LD_PRELOADcơ chế để chèn thư viện tải động của chính nó libstdbuf.so. Điều này có nghĩa là nó sẽ không hoạt động với các loại thực thi này: với tập hợp khả năng tập tin hoặc tập tin, được liên kết tĩnh, không sử dụng libc tiêu chuẩn. Trong những trường hợp này, tốt hơn là sử dụng các giải pháp với unbuffer/ script/ socat. Xem thêm stdbuf với setuid / ability .
pabouk

75

Một cách khác để bật chế độ đầu ra đệm dòng cho long_running_commandlà sử dụng scriptlệnh chạy long_running_commandtrong thiết bị đầu cuối giả (pty).

script -q /dev/null long_running_command | print_progress      # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress    # Linux

15
+1 mẹo hay, vì scriptlà một lệnh cũ như vậy, nên nó có sẵn trên tất cả các nền tảng giống như Unix.
Aaron Digulla

5
bạn cũng cần -qtrên Linux:script -q -c 'long_running_command' /dev/null | print_progress
jfs

1
Có vẻ như tập lệnh đọc từ stdin, điều này khiến cho không thể chạy như vậy long_running_commandtrong nền, ít nhất là khi bắt đầu từ thiết bị đầu cuối tương tác. Để giải quyết, tôi đã có thể chuyển hướng stdin từ /dev/null, vì tôi long_running_commandkhông sử dụng stdin.
haridsv

1
Thậm chí hoạt động trên Android.
not2qubit

3
Một nhược điểm đáng kể: ctrl-z không còn hoạt động (nghĩa là tôi không thể tạm dừng tập lệnh). Điều này có thể được sửa bởi, ví dụ: echo | tập lệnh sudo -c / usr / local / bin / ec2-snapshot-all / dev / null | ts, nếu bạn không phiền không thể tương tác với chương trình.
rlpowell 24/07/2015

66

Đối với grep, sedawkbạn có thể buộc đầu ra được đệm dòng. Bạn có thể dùng:

grep --line-buffered

Buộc đầu ra được đệm dòng. Theo mặc định, đầu ra được đệm dòng khi đầu ra tiêu chuẩn là một thiết bị đầu cuối và khối được đệm khác.

sed -u

Tạo dòng đầu ra được đệm.

Xem trang này để biết thêm thông tin: http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html


51

Nếu đó là một vấn đề với libc sửa đổi bộ đệm / xả của nó khi đầu ra không đi đến một thiết bị đầu cuối, bạn nên thử socat . Bạn có thể tạo một luồng hai chiều giữa hầu hết mọi loại cơ chế I / O. Một trong số đó là một chương trình rẽ nhánh nói với một tty giả.

 socat EXEC:long_running_command,pty,ctty STDIO 

Những gì nó làm là

  • tạo ra một tty giả
  • fork long_rasty_command với phần phụ của pty là stdin / stdout
  • thiết lập luồng hai chiều giữa phía chính của pty và địa chỉ thứ hai (ở đây là STDIO)

Nếu điều này cung cấp cho bạn cùng một đầu ra như long_running_command, thì bạn có thể tiếp tục với một đường ống.

Chỉnh sửa: Wow Không thấy câu trả lời unbuffer! Chà, dù sao thì socat cũng là một công cụ tuyệt vời, vì vậy tôi có thể để lại câu trả lời này


1
... và tôi không biết gì về socat - có vẻ như netcat chỉ có lẽ còn hơn thế. ;) Cảm ơn và +1.

3
Tôi sẽ sử dụng socat -u exec:long_running_command,pty,end-close -ở đây
Stéphane Chazelas 7/08/2015

20

Bạn có thể dùng

long_running_command 1>&2 |& print_progress

Vấn đề là libc sẽ đệm dòng khi xuất ra màn hình và toàn bộ đệm khi xuất ra tệp. Nhưng không có bộ đệm cho stderr.

Tôi không nghĩ đó là vấn đề với bộ đệm ống, tất cả là về chính sách bộ đệm của libc.


Bạn đúng; Câu hỏi của tôi vẫn là: Làm thế nào tôi có thể ảnh hưởng đến chính sách bộ đệm của libc mà không cần biên dịch lại?
Aaron Digulla

@ StéphaneChazelas fd1 sẽ được chuyển hướng đến stderr
Wang HongQin

@ StéphaneChazelas tôi không nhận được điểm tranh luận của bạn. Xin hãy làm một bài kiểm tra, nó hoạt động
Wang HongQin

3
OK, điều đang xảy ra là với cả hai zsh( |&xuất phát từ csh) và bashkhi bạn thực hiện cmd1 >&2 |& cmd2, cả fd 1 và 2 đều được kết nối với thiết bị xuất chuẩn bên ngoài. Vì vậy, nó hoạt động trong việc ngăn chặn bộ đệm khi thiết bị xuất chuẩn bên ngoài đó là một thiết bị đầu cuối, nhưng chỉ vì đầu ra không đi qua đường ống (nên print_progresskhông in gì cả). Vì vậy, nó giống như long_running_command & print_progress(ngoại trừ print_proTHER stdin là một đường ống không có người viết). Bạn có thể xác minh ls -l /proc/self/fd >&2 |& catso với ls -l /proc/self/fd |& cat.
Stéphane Chazelas

3
Đó là vì |&viết tắt của 2>&1 |, theo nghĩa đen. Thế cmd1 |& cmd2là xong cmd1 1>&2 2>&1 | cmd2. Vì vậy, cả fd 1 và 2 cuối cùng được kết nối với stderr ban đầu, và không có gì được viết vào đường ống. ( s/outer stdout/outer stderr/gtrong bình luận trước của tôi).
Stéphane Chazelas

11

Nó đã từng là trường hợp, và có lẽ vẫn là trường hợp, khi đầu ra tiêu chuẩn được ghi vào một thiết bị đầu cuối, nó được đệm theo mặc định - khi một dòng mới được viết, dòng được ghi vào thiết bị đầu cuối. Khi đầu ra tiêu chuẩn được gửi đến một đường ống, nó được đệm hoàn toàn - vì vậy dữ liệu chỉ được gửi đến quy trình tiếp theo trong đường ống khi bộ đệm I / O tiêu chuẩn được lấp đầy.

Đó là nguồn gốc của rắc rối. Tôi không chắc liệu bạn có thể làm gì nhiều để khắc phục nó mà không sửa đổi chương trình ghi vào đường ống. Bạn có thể sử dụng setvbuf()chức năng với _IOLBFcờ để đặt vô điều kiện stdoutvào chế độ đệm dòng. Nhưng tôi không thấy một cách dễ dàng để thực thi điều đó trên một chương trình. Hoặc chương trình có thể thực hiện fflush()tại các điểm thích hợp (sau mỗi dòng đầu ra), nhưng áp dụng cùng một nhận xét.

Tôi cho rằng nếu bạn thay thế đường ống bằng thiết bị đầu cuối giả, thì thư viện I / O tiêu chuẩn sẽ nghĩ đầu ra là một thiết bị đầu cuối (vì đó là một loại thiết bị đầu cuối) và sẽ tự động đệm dòng. Đó là một cách phức tạp để đối phó với mọi thứ, mặc dù.


7

Tôi biết đây là một câu hỏi cũ và đã có rất nhiều câu trả lời, nhưng nếu bạn muốn tránh vấn đề về bộ đệm, chỉ cần thử một cái gì đó như:

stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt

Điều này sẽ xuất ra các bản ghi thời gian thực và cũng lưu chúng vào logs.txttệp và bộ đệm sẽ không còn ảnh hưởng đến tail -flệnh.


4
Đây giống như câu trả lời thứ hai: - /
Aaron Digulla

2
stdbuf được bao gồm trong gnu coreutils (Tôi đã xác minh trên phiên bản mới nhất 8.25). xác minh điều này hoạt động trên một linux nhúng.
zhaorufei

Từ tài liệu của stdbuf, NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for example) then that will override corresponding changes by 'stdbuf'.
shrewmouse

6

Tôi không nghĩ rằng vấn đề là với đường ống. Có vẻ như quá trình chạy dài của bạn không thường xuyên xả bộ đệm của chính nó. Thay đổi kích thước bộ đệm của đường ống sẽ là một hack để làm tròn nó, nhưng tôi không nghĩ nó có thể mà không cần xây dựng lại kernel - điều mà bạn không muốn làm như một hack, vì nó có thể ảnh hưởng đến rất nhiều quá trình khác.


18
Nguyên nhân sâu xa là libc chuyển sang bộ đệm 4k nếu thiết bị xuất chuẩn không phải là tty.
Aaron Digulla

5
Điều đó rất thú vị ! bởi vì đường ống không gây ra bất kỳ bộ đệm. Chúng cung cấp bộ đệm, nhưng nếu bạn đọc từ một đường ống, bạn sẽ nhận được bất kỳ dữ liệu nào có sẵn, bạn không phải chờ bộ đệm trong đường ống. Vì vậy, thủ phạm sẽ là bộ đệm stdio trong ứng dụng.

3

Theo bài đăng này ở đây , bạn có thể thử giảm ulimit ống xuống một khối 512 byte duy nhất. Nó chắc chắn sẽ không tắt bộ đệm, nhưng tốt, 512 byte là ít hơn 4K: 3


3

Theo cách tương tự với câu trả lời của chad , bạn có thể viết một đoạn script nhỏ như thế này:

# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'

Sau đó sử dụng scripteelệnh này để thay thế cho tee.

my-long-running-command | scriptee

Than ôi, tôi dường như không thể có được một phiên bản như thế để hoạt động hoàn hảo trong Linux, vì vậy dường như bị giới hạn ở các bản unix kiểu BSD.

Trên Linux, điều này đã gần, nhưng bạn không nhận lại lời nhắc khi kết thúc (cho đến khi bạn nhấn enter, v.v.) ...

script -q -c 'cat > /proc/self/fd/1' /dev/null

Tại sao nó hoạt động? "Kịch bản" có tắt bộ đệm không?
Aaron Digulla

@Aaron Digulla: scriptgiả lập một thiết bị đầu cuối, vì vậy, tôi tin rằng nó tắt bộ đệm. Nó cũng lặp lại từng ký tự được gửi cho nó - đó là lý do tại sao catđược gửi đến /dev/nulltrong ví dụ. Theo như chương trình đang chạy bên trong script, nó đang nói đến một phiên tương tác. Tôi tin rằng nó tương tự như expectvề vấn đề này, nhưng scriptcó khả năng là một phần của hệ thống cơ sở của bạn.
JWD

Lý do tôi sử dụng teelà để gửi một bản sao của luồng đến một tệp. Tập tin được chỉ định ở scripteeđâu?
Bruno Bronosky

@BrunoBronosky: Bạn nói đúng, đó là một tên xấu cho chương trình này. Nó không thực sự hoạt động 'tee'. Nó chỉ là vô hiệu hóa bộ đệm của đầu ra, theo câu hỏi ban đầu. Có lẽ nó nên được gọi là "scriptcat" (mặc dù nó cũng không được ghép nối ...). Bất kể, bạn có thể thay thế catlệnh bằng tee myfile.txtvà bạn sẽ nhận được hiệu ứng bạn muốn.
JWD

2

Tôi tìm thấy giải pháp thông minh này: (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

Đây là mẹo. catsẽ đọc đầu vào bổ sung (cho đến EOF) và chuyển nó vào đường ống sau khi echođã đưa các đối số của nó vào luồng đầu vào shell_executable.


2
Trên thực tế, catkhông thấy đầu ra của echo; bạn chỉ cần chạy hai lệnh trong một khung con và đầu ra của cả hai được gửi vào đường ống. Lệnh thứ hai trong subshell ('cat') đọc từ cha / stdin bên ngoài, đó là lý do tại sao nó hoạt động.
Aaron Digulla

0

Theo này kích thước bộ đệm ống dường như được thiết lập trong hạt nhân và sẽ yêu cầu bạn phải biên dịch lại kernel của bạn để thay đổi.


7
Tôi tin rằng đó là một bộ đệm khác nhau.
Phường Samuel Edwin
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.