Boruda tamponlamayı kapat


395

İki komutu çağıran bir betiğim var:

long_running_command | print_progress

long_running_commandBaskılar bir ilerleme ama onunla mutsuzum. print_progressDaha güzel yapmak için kullanıyorum (yani, ilerlemeyi tek bir satırda basıyorum).

Sorun: Stdout'a bir boru bağlamak da 4K'lık bir tamponu aktive eder, hoş baskı programına hiçbir şey alamaz ... hiçbir şey ... hiçbir şey ... bir sürü ... :)

4K arabelleğini nasıl devre dışı bırakabilirim long_running_command(hayır, kaynağa sahip değilim)?


1
Böylece, long_running_command'ı piposuz çalıştırdığınızda, ilerleme güncellemelerini doğru bir şekilde görebilirsiniz, ancak borular pipolandığında tamponlanır mı?

1
Evet, aynen böyle olur.
Aaron Digulla

20
Tamponlamayı kontrol etmenin basit bir yolunun yetersizliği on yıllardır bir problem olmuştur. Örneğin, bakınız: marc.info/?l=glibc-bug&m=98313957306297&w=4 ikonların diyor "Burada bunu yaparken arsed olamaz ve konumumu haklı bazı alkış-tuzak"


1
Aslında yeterli veri beklenirken gecikmeye neden olan boru değil stdio. Borular kapasiteye sahiptir, ancak boruya yazılan herhangi bir veri olduğu anda, diğer uçtan hemen okumaya hazırdır.
Sam Watkins

Yanıtlar:


254

unbufferKomutu kullanabilirsiniz ( expectpaketin bir parçası olarak gelir ), örneğin

unbuffer long_running_command | print_progress

unbufferlong_running_commandsistemin bir etkileşimli işlem olarak ele almasını sağlayan bir psödoterminal (pty) ile bağlanır , bu nedenle, ertelemenin olası nedeni olan boru hattında 4-kiB tamponlama kullanılmaz.

Daha uzun boru hatları için, her bir komutun çözümünün kaldırılması gerekebilir (sonuncusu hariç), örn.

unbuffer x | unbuffer -p y | z

3
Aslında, etkileşimli işlemlere bağlanmak için küçük bir miktar kullanılması genel olarak beklenilen doğrudur.

15
Boru hattı ara çözücüye çağırdığında, -p argümanını kullanmalısınız ki böylelikle çözelti stdin'den okunmalı.

26
Not: debian sistemlerde, bu denir expect_unbufferve olduğu expect-devdeğil, paketin expectpaket
bdonlan

4
@bdonlan: En az Ubuntu (debian bazlı), expect-devhem de sağlar unbufferve expect_unbuffer(eski ikinci bir sembolik olan). Linkler expect 5.44.1.14-1(2009) dan beri mevcuttur .
jfs

1
Not: Ubuntu 14.04.x ​​sistemlerinde, ayrıca bekle-dev paketinde de bulunur.
Alexandre Mazel

462

Bu kediyi cildin başka bir yolu stdbufGNU Coreutils'in bir parçası olan programı kullanmaktır (FreeBSD'nin de kendine ait olanı vardır).

stdbuf -i0 -o0 -e0 command

Bu, giriş, çıkış ve hata için tamponlamayı tamamen kapatır. Bazı uygulamalar için, çizgi tamponlama performans nedenleriyle daha uygun olabilir:

stdbuf -oL -eL command

Yalnızca dinamik olarak bağlı uygulamalar için stdioarabelleğe alma ( printf(), fputs()...) için çalıştığını ve yalnızca bu uygulamanın standart akışlarının arabelleğe alınmasını kendi başınıza ayarlamamasına rağmen, çoğu uygulamayı kapsaması gerektiğini unutmayın.


6
"unbuffer" paketinin içinde yer alan Ubuntu'ya kurulmalı: 2MB olan expect-dev ...
lepe

2
Bu, varsayılan raspbian yüklemesinde arabellek günlüğe kaydetme işleminde harika çalışıyor. sudo stdbuff … commandBulmamama rağmen işler buldum stdbuff … sudo command.
natevw

20
@qdii ile stdbufçalışmaz tee, çünkü teetarafından ayarlanan varsayılanların üzerine yazar stdbuf. İçin kılavuz sayfasına bakınız stdbuf.
51'de ceving

5
@ Bizarrely, unbuffer'ın x11 ve tcl / tk bağımlılıkları vardır, yani bunlar olmadan bir sunucuya yüklüyorsanız gerçekten> 80 MB olması gerekir.
jpatokal

10
@qdii kendi dinamik olarak yüklenmiş kütüphanesini yerleştirmek stdbufiçin LD_PRELOADmekanizma kullanır libstdbuf.so. Bu, bu tür çalıştırılabilir dosyalarla çalışmayacağı anlamına gelir: setuid veya dosya özellikleri ayarlanmış, statik olarak bağlanmış, standart libc kullanılmamış. Bu gibi durumlarda unbuffer/ script/ ile çözümleri kullanmak daha iyidir socat. Ayrıca bkz. Setuid / yetenekleri olan stdbuf .
pabouk 12:15

75

Bunun için hat tamponlama çıkış modunu açmanın bir başka yolu da, sözde bir terminalde (pty) çalışan komutu long_running_commandkullanmaktır .scriptlong_running_command

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 güzel numara, scripteski bir komut olduğundan, Unix benzeri tüm platformlarda kullanılabilir olması gerekir.
Aaron Digulla

5
ayrıca -qLinux'a da ihtiyacınız var:script -q -c 'long_running_command' /dev/null | print_progress
jfs 11:13

1
En azından etkileşimli terminalden başladığında, arka planda stdinböyle bir long_running_commandarka plan çalıştırmayı imkansız kılan senaryodan okuyor gibi görünüyor . Geçici çözüm için, stdin'i yöneltmeyi başardım /dev/null, çünkü long_running_commandkullanmadım stdin.
haridsv

1
Android'de bile çalışıyor.
not2qubit

3
Önemli bir dezavantaj: ctrl-z artık çalışmıyor (yani senaryoyu askıya alamıyorum). Bu, örneğin: echo | sudo betiği -c / usr / yerel / bin / ec2-snapshot-all / dev / null | ts, programla etkileşime girememenin sakıncası yoksa.
rlpowell

66

İçin grep, sedve awkçıkış hattı tamponlu edilecek zorlayabilir. Kullanabilirsiniz:

grep --line-buffered

Çıktıyı arabelleğe alınmaya zorla. Varsayılan olarak, standart çıktı bir terminal olduğunda çıktı arabelleğe alınır ve aksi takdirde blok arabelleğe alınır.

sed -u

Çıkış hattını arabelleğe al.

Daha fazla bilgi için bu sayfaya bakın: http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html


51

Çıktı bir terminale gitmediğinde tamponun / temizlemenin değiştirilmesi libc ile ilgili bir problemse, socat'ı denemelisiniz . Neredeyse tüm G / Ç mekanizmaları arasında çift yönlü bir akış oluşturabilirsiniz. Bunlardan biri sahte bir tty ile konuşan çatallı bir programdır.

 socat EXEC:long_running_command,pty,ctty STDIO 

Ne yapar

  • sözde tty oluşturmak
  • çatal long_running_command pty köle tarafı ile stdin / stdout olarak
  • pty'nin ana tarafı ile ikinci adres arasında çift yönlü bir akış oluşturmak (burada STDIO'dur)

Bu size aynı çıktıyı verirse long_running_command, bir boruya devam edebilirsiniz.

Düzenleme: Vay unbuffer yanıtını görmedim! Sosyal, zaten harika bir araçtır, bu yüzden bu cevabı bırakabilirim.


1
... ve socat'ı bilmiyordum - netcat gibi görünüyor, belki de daha fazla. ;) Teşekkürler ve +1.

3
socat -u exec:long_running_command,pty,end-close -Burada kullanırdım
Stéphane Chazelas

20

Kullanabilirsiniz

long_running_command 1>&2 |& print_progress

Sorun, libc'nin ekrana stdout yapıldığında satır arabelleğini ve bir dosyaya stdout olduğunda tam arabelleğe almasıdır. Fakat stderr için tampon yok.

Boru tamponu ile ilgili bir sorun olduğunu sanmıyorum, hepsi libc'in tampon politikası ile ilgili.


Haklısın; Sorum şu: Hala yeniden derlemeden libc'nin tampon politikasını nasıl etkileyebilirim?
Aaron Digulla

@ StéphaneChazelas fd1, stderr'e yönlendirilecek
Wang HongQin

@ StéphaneChazelas ben senin tartışma noktasını anlamıyorum. plz bir test yapın, işe yarıyor
Wang HongQin

3
Tamam, neler oluyor bu ikisi ile olmasıdır zsh(burada |&itibaren csh'ın uyarlanan geliyor) ve bashyapmanız zaman cmd1 >&2 |& cmd2, hem fd 1 ve 2 dış Stdout'a bağlanır. Bu nedenle, dış stdout bir terminal olduğunda tamponlamanın önlenmesi için çalışır, ancak yalnızca çıktı borudan geçmediği için (bu nedenle print_progresshiçbir şey yazdırmaz). Yani aynı long_running_command & print_progress(print_progress stdin'in yazarı olmayan bir boru olması dışında) ile aynı. İle ls -l /proc/self/fd >&2 |& catkarşılaştırıldığında doğrulayabilirsiniz ls -l /proc/self/fd |& cat.
Stéphane Chazelas

3
Çünkü tam anlamıyla bunun |&için kısa 2>&1 |. Yani cmd1 |& cmd2bir cmd1 1>&2 2>&1 | cmd2. Böylece, hem fd 1 hem de 2, orijinal stderr'e bağlanır ve boruya hiçbir şey kalmaz. ( s/outer stdout/outer stderr/gönceki yorumuma göre).
Stéphane Chazelas

11

Durum buydu ve muhtemelen hala durum böyledir, standart çıktı bir terminale yazıldığında, varsayılan olarak satır tamponlanır - yeni bir satır yazıldığında, satırın terminale yazılması. Standart çıktı bir boruya gönderildiğinde, tamamen tamponlanır - bu nedenle standart I / O tamponu dolduğunda veriler sadece boru hattındaki bir sonraki işleme gönderilir.

Bu sorunun kaynağı. Programın boruya yazılmasını değiştirmeden düzeltmek için yapabileceğiniz çok şey olup olmadığından emin değilim. Koşulsuzca satır arabelleğe alma moduna geçirmek için setvbuf()işlevi _IOLBFbayrakla birlikte kullanabilirsiniz stdout. Ancak bunu bir programda uygulamanın kolay bir yolunu görmüyorum. Veya program fflush()uygun noktalarda (her çıktı satırından sonra) yapabilir, ancak aynı yorum geçerlidir.

Boruyu sahte terminalle değiştirirseniz, standart G / Ç kütüphanesinin çıktının bir terminal olduğunu düşüneceğini (bunun bir tür terminal olduğundan) ve otomatik olarak tampon arabelleği olacağını varsayalım. Bununla birlikte, bu şeylerle başa çıkmanın karmaşık bir yoludur.


7

Bunun eski bir soru olduğunu biliyorum ve çok fazla cevabı vardı ama arabellek probleminden kaçınmak istiyorsanız, sadece şöyle bir şey deneyin:

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

Bu, gerçek zamanlı olarak günlükleri çıkarır ve ayrıca bunları logs.txtdosyaya kaydeder ve arabellek artık tail -fkomutu etkilemez .


4
Bu ikinci cevaba benziyor: - /
Aaron Digulla

2
stdbuf, gnu coreutils'e dahil edilmiştir (en son 8.25 sürümünde doğruladım). Bunun gömülü bir linux üzerinde çalıştığını doğruladı.
zhaorufei

Stdbuf belgelerine, NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for example) then that will override corresponding changes by 'stdbuf'.
shrewmouse

6

Sorunun boruyla ilgili olduğunu sanmıyorum. Uzun süren işleminiz kendi tamponunu yeterince sık temizlemiyor gibi geliyor. Borunun tampon boyutunu değiştirmek, etrafını sarmak için bir hack olurdu, ancak çekirdeği yeniden inşa etmeden mümkün olabileceğini sanmıyorum - hack olarak yapmak istemeyeceğiniz bir şey, muhtemelen aversley birçok başka işlemi etkiliyor.


18
Kök neden, stdout bir tty değilse libc'nin 4k tamponlamaya geçmesidir.
Aaron Digulla

5
Bu çok ilginç ! çünkü boru tamponlamaya neden olmaz. Tamponlama sağlarlar, ancak bir pipodan okursanız, elinizde ne olursa olsun elde edersiniz, pipodaki bir tamponu beklemeniz gerekmez. Dolayısıyla suçlu, başvuruda stdio tamponlaması olacaktır.

3

Buradaki yazıya göre , boru ulimitini bir tek 512 bayt bloğa indirmeyi deneyebilirsiniz. Tamponlama kesinlikle kapanmayacak, fakat 512 bayt 4K: 3'ten daha az.


3

Çad'ın cevabına benzeyen bir damarda , bunun gibi küçük bir senaryo yazabilirsiniz:

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

Sonra bu scripteekomutu yerine kullanabilirsiniz tee.

my-long-running-command | scriptee

Ne yazık ki, Linux'ta kusursuz çalışmak için böyle bir sürüm elde edemiyorum, bu yüzden BSD tarzı teksirler ile sınırlı görünüyor.

Linux'ta, bu yakın, ancak bittiğinde komut isteminizi geri alamazsınız (enter tuşuna basıncaya kadar) ...

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

Bu neden işe yarıyor? "Script" tamponlamayı kapatıyor mu?
Aaron Digulla,

@Aaron Digulla: scriptBir terminali öykünüyor , bu yüzden evet, arabelleğe almanın kapandığını düşünüyorum. Ayrıca kendisine gönderilen her karakteri de yankılanır - bu nedenle örnekte catgönderilir /dev/null. İçeride çalışan program scriptsöz konusu olduğunda, etkileşimli bir oturumla konuşuyor. Sanırım expectbu konuda benzer , ancak scriptbüyük olasılıkla temel sisteminizin bir parçası.
jwd

Kullanmamın nedeni teeakışın bir kopyasını bir dosyaya göndermek. Dosya nerede belirtilir scriptee?
Bruno Bronosky

@BrunoBronosky: Haklısın, bu program için kötü bir isim. Gerçekten bir 'tee' işlemi yapmıyor. Bu sadece orijinal soru başına çıktının tamponlanmasını engelliyor. Belki de "scriptcat" olarak adlandırılmalıdır (birleştirme yapmamasına rağmen ...). Ne olursa olsun, catkomutu ile değiştirebilirsiniz tee myfile.txtve istediğiniz efekti elde etmelisiniz.
jwd

2

Bu akıllı çözümü buldum: (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

Bu hile yapar. catek girişi okuyacak (EOF'ye kadar) ve echobunu argümanlarını giriş akışına koyduktan sonra boruya iletecektir shell_executable.


2
Aslında, catçıktısını görmüyor echo; Sadece bir alt kabukta iki komut çalıştırıyorsunuz ve her ikisinin de çıkışı boruya gönderiliyor. Alt kabuktaki ikinci komut ('cat') üst / dış stdin'den okur, bu yüzden işe yarar.
Aaron Digulla,

0

Göre bu boru tampon boyutu çekirdekte ayarlanmış gibi görünüyor ve değiştirmek için çekirdek derlemek gerektirecektir.


7
Bunun farklı bir tampon olduğuna inanıyorum.
Samuel Edwin Ward
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.