Wyłącz buforowanie w rurze


395

Mam skrypt, który wywołuje dwa polecenia:

long_running_command | print_progress

Te long_running_commandodciski postęp, ale jestem zadowolony z niego. Używam, print_progressaby uczynić to ładniejszym (mianowicie, drukuję postęp w jednym wierszu).

Problem: Podłączenie rury do standardowego wyjścia również aktywuje bufor 4K, do ładnego programu do drukowania nic nie dostaje ... nic ... nic ... dużo ... :)

Jak mogę wyłączyć bufor 4K dla long_running_command(nie, nie mam źródła)?


1
Więc kiedy uruchomisz komendę long_running_comming bez pipowania, możesz poprawnie zobaczyć postępy aktualizacji, ale kiedy są one buforowane?

1
Tak, dokładnie tak się dzieje.
Aaron Digulla,

20
Niemożność prostego sposobu kontrolowania buforowania była problemem od dziesięcioleci. Na przykład: marc.info/?l=glibc-bug&m=98313957306297&w=4, który w zasadzie mówi: „Nie mogę się na to zgodzić, a oto kilka pułapek, które uzasadniają moją pozycję”


1
Właściwie to nie stdio powoduje opóźnienie podczas oczekiwania na wystarczającą ilość danych. Rury mają pojemność, ale jak tylko dane zostaną zapisane w rurze, jest natychmiast gotowe do odczytu na drugim końcu.
Sam Watkins,

Odpowiedzi:


254

Możesz użyć unbufferpolecenia (które jest częścią expectpakietu), np

unbuffer long_running_command | print_progress

unbufferłączy się long_running_commandz pseudoterminalem (pty), co sprawia, że ​​system traktuje go jako proces interaktywny, dlatego nie wykorzystuje buforowania 4-kiB w potoku, który jest prawdopodobną przyczyną opóźnienia.

W przypadku dłuższych potoków może być konieczne cofnięcie buforowania każdego polecenia (z wyjątkiem ostatniego), np

unbuffer x | unbuffer -p y | z

3
W rzeczywistości użycie pty do połączenia z interaktywnymi procesami jest ogólnie rzecz biorąc oczekiwane.

15
Podczas wywoływania potoków w celu rozpakowywania należy użyć argumentu -p, aby rozpakowywanie odczytywał ze standardowego wejścia.

26
Uwaga: W systemach Debian jest to wywoływane expect_unbufferi znajduje się w expect-devpakiecie, a nie w expectpakiecie
bdonlan

4
@bdonlan: Przynajmniej na Ubuntu (oparty na Debianie), expect-devzapewnia oba unbufferi expect_unbuffer(ten pierwszy jest dowiązaniem symbolicznym do drugiego). Linki są dostępne od expect 5.44.1.14-1(2009).
jfs

1
Uwaga: W systemach Ubuntu 14.04.x ​​znajduje się również w pakiecie expect-dev.
Alexandre Mazel,

462

Innym sposobem na skórowanie tego kota jest użycie stdbufprogramu, który jest częścią GNU Coreutils (FreeBSD ma również swój własny).

stdbuf -i0 -o0 -e0 command

To całkowicie wyłącza buforowanie danych wejściowych, wyjściowych i błędów. W przypadku niektórych aplikacji buforowanie linii może być bardziej odpowiednie ze względu na wydajność:

stdbuf -oL -eL command

Pamiętaj, że działa tylko w przypadku stdiobuforowania ( printf(), fputs()...) w przypadku dynamicznie połączonych aplikacji i tylko wtedy, gdy aplikacja sama nie dostosuje buforowania swoich standardowych strumieni, chociaż powinno to obejmować większość aplikacji.


6
„unbuffer” musi być zainstalowany w Ubuntu, który znajduje się w pakiecie: expect-dev, który ma 2 MB ...
lepe

2
Działa to świetnie w przypadku domyślnej instalacji raspbian w celu rozpakowywania rejestrowania. Znalazłem sudo stdbuff … commandprace, chociaż stdbuff … sudo commandnie.
natevw

20
@qdii stdbufnie działa tee, ponieważ teezastępuje wartości domyślne ustawione przez stdbuf. Zobacz stronę podręcznika użytkownika stdbuf.
ceving

5
@lepe Dziwnie, rozpakowywanie ma zależności od x11 i tcl / tk, co oznacza, że ​​faktycznie potrzebuje> 80 MB, jeśli instalujesz go na serwerze bez nich.
jpatokal

10
@qdii stdbufużywa LD_PRELOADmechanizmu do wstawiania własnej dynamicznie ładowanej biblioteki libstdbuf.so. Oznacza to, że nie będzie działać z tego rodzaju plikami wykonywalnymi: z ustawionymi możliwościami setuid lub file, statycznie połączonymi, nieużywającymi standardowego libc. W takich przypadkach lepiej jest używać rozwiązań z unbuffer/ script/ socat. Zobacz także stdbuf z setuid / Możliwościami .
pabouk

75

Jeszcze innym sposobem włączenia trybu wyjścia buforowania linii dla long_running_commandjest użycie scriptpolecenia, które uruchamia twój long_running_commandpseudo terminal (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 fajna sztuczka, ponieważ scriptjest to tak stare polecenie, powinno być dostępne na wszystkich platformach uniksowych.
Aaron Digulla,

5
potrzebujesz także -qw systemie Linux:script -q -c 'long_running_command' /dev/null | print_progress
jfs

1
Wygląda na to, że skrypt czyta z stdin, co uniemożliwia uruchomienie takiego long_running_commandw tle, przynajmniej po uruchomieniu z terminalu interaktywnego. Aby obejść ten problem , mogłem przekierować standardowe wejście /dev/null, ponieważ moje long_running_commandnie używa stdin.
haridsv

1
Działa nawet na Androidzie.
not2qubit

3
Jedna znacząca wada: ctrl-z już nie działa (tzn. Nie mogę zawiesić skryptu). Można to naprawić na przykład: echo | skrypt sudo -c / usr / local / bin / ec2-snapshot-all / dev / null | ts, jeśli nie masz nic przeciwko niemożności interakcji z programem.
rlpowell

66

Na grep, sedi awkmożna wymusić wyjście być linia buforowane. Możesz użyć:

grep --line-buffered

Wymusza buforowanie wyjścia. Domyślnie wyjście jest buforowane liniowo, gdy standardowe wyjście jest terminalem, a blok buforowany w inny sposób.

sed -u

Spraw, aby linia wyjściowa była buforowana.

Zobacz tę stronę, aby uzyskać więcej informacji: http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html


51

Jeśli jest problem z libc modyfikującym buforowanie / opróżnianie, gdy dane wyjściowe nie trafiają do terminala, powinieneś spróbować socat . Możesz utworzyć dwukierunkowy strumień między prawie każdym rodzajem mechanizmu we / wy. Jednym z nich jest rozwidlony program mówiący do pseudo tty.

 socat EXEC:long_running_command,pty,ctty STDIO 

Co to robi

  • utwórz pseudo tty
  • fork long_running_command z niewolniczą stroną pty jako stdin / stdout
  • ustanowić dwukierunkowy strumień między stroną główną pty a drugim adresem (tutaj jest STDIO)

Jeśli daje to taką samą wydajność jak long_running_command, możesz kontynuować z potokiem.

Edycja: Wow Nie widziałem odpowiedzi niebuforowanej! Cóż, socat i tak jest świetnym narzędziem, więc mogę zostawić tę odpowiedź


1
... i nie wiedziałem o socat - może trochę bardziej przypomina netcat. ;) Dzięki i +1.

3
Użyłbym socat -u exec:long_running_command,pty,end-close -tutaj
Stéphane Chazelas

20

Możesz użyć

long_running_command 1>&2 |& print_progress

Problem polega na tym, że libc będzie buforował linię, gdy standardowe wyjście do ekranu, i pełny bufor, gdy standardowe wyjście do pliku. Ale brak bufora dla stderr.

Nie sądzę, że to jest problem z buforem potoku, wszystko dotyczy polityki buforów libc.


Masz rację; wciąż mam pytanie: jak mogę wpłynąć na zasady buforowania libc bez ponownej kompilacji?
Aaron Digulla

@ StéphaneChazelas fd1 zostanie przekierowany do stderr
Wang HongQin

@ StéphaneChazelas Nie rozumiem, o co ci chodzi. proszę zrobić test, to działa
Wang HongQin

3
OK, dzieje się tak, że z obydwoma zsh(skąd |&pochodzi adaptacja z csh), a bashkiedy to zrobisz cmd1 >&2 |& cmd2, oba fd 1 i 2 są podłączone do zewnętrznego wyjścia. Działa więc w zapobieganiu buforowaniu, gdy ten zewnętrzny stdout jest terminalem, ale tylko dlatego, że wyjście nie przechodzi przez potok (więc print_progressnic nie drukuje). Więc to jest tak samo jak long_running_command & print_progress(z wyjątkiem tego, że print_progress stdin jest potokiem, który nie ma programu piszącego). Możesz zweryfikować w ls -l /proc/self/fd >&2 |& catporównaniu do ls -l /proc/self/fd |& cat.
Stéphane Chazelas,

3
To dlatego , że dosłownie |&jest skrótem 2>&1 |. Tak cmd1 |& cmd2jest cmd1 1>&2 2>&1 | cmd2. Tak więc, zarówno fd 1, jak i 2 kończą się na oryginalnym stderr, i nic nie zostaje zapisywane na rurze. ( s/outer stdout/outer stderr/gw moim poprzednim komentarzu).
Stéphane Chazelas

11

Kiedyś tak było i prawdopodobnie nadal tak jest, że kiedy standardowe wyjście jest zapisywane na terminalu, to jest ono domyślnie buforowane w linii - kiedy zapisywany jest nowy wiersz, wiersz jest zapisywany na terminalu. Gdy standardowe wyjście jest wysyłane do potoku, jest w pełni buforowane - więc dane są wysyłane do następnego procesu w potoku, gdy standardowy bufor We / Wy jest wypełniony.

To jest źródłem kłopotów. Nie jestem pewien, czy można wiele zrobić, aby to naprawić bez modyfikacji programu zapisującego w potoku. Możesz użyć setvbuf()funkcji z _IOLBFflagą, aby bezwarunkowo przejść stdoutdo trybu buforowania linii. Ale nie widzę łatwego sposobu na wymuszenie tego w programie. Lub program może zrobić fflush()w odpowiednich punktach (po każdym wierszu wyniku), ale stosuje się ten sam komentarz.

Przypuszczam, że jeśli zastąpisz potok pseudo-terminalem, wówczas standardowa biblioteka I / O uznałaby, że wyjście jest terminalem (ponieważ jest to rodzaj terminala) i automatycznie buforowałaby linię. Jest to jednak złożony sposób radzenia sobie z rzeczami.


7

Wiem, że to stare pytanie i już miałem wiele odpowiedzi, ale jeśli chcesz uniknąć problemu z buforem, po prostu spróbuj czegoś takiego:

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

Spowoduje to wyświetlenie dzienników w czasie rzeczywistym, a także zapisanie ich w logs.txtpliku, a bufor nie będzie już wpływał na tail -fpolecenie.


4
To wygląda jak druga odpowiedź: - /
Aaron Digulla,

2
stdbuf jest zawarty w GNU Coreutils (zweryfikowałem na najnowszej wersji 8.25). sprawdzono, że działa na osadzonym systemie Linux.
zhaorufei

Z dokumentacji 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

Nie sądzę, że problem tkwi w fajce. Wygląda na to, że twój długotrwały proces nie czyści wystarczająco własnego bufora. Zmiana rozmiaru bufora potoku byłaby włamaniem do obejścia go, ale nie sądzę, że jest to możliwe bez przebudowania jądra - coś, czego nie chciałbyś robić jako włamania, ponieważ prawdopodobnie wywrze negatywny wpływ na wiele innych procesów.


18
Główną przyczyną jest to, że libc przełącza się na buforowanie 4k, jeśli standardowe wyjście nie jest tty.
Aaron Digulla,

5
To jest bardzo interesujące ! ponieważ potok nie powoduje buforowania. Zapewniają buforowanie, ale jeśli czytasz z potoku, dostajesz wszystkie dostępne dane, nie musisz czekać na bufor w potoku. Winowajcą byłoby więc buforowanie stdio w aplikacji.

3

Zgodnie z tym postem tutaj możesz spróbować zmniejszyć ulimit rury do jednego pojedynczego 512-bajtowego bloku. Z pewnością nie wyłączy buforowania, ale cóż, 512 bajtów to znacznie mniej niż 4K: 3


3

Podobnie jak odpowiedź Czada , możesz napisać taki skrypt:

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

Następnie użyj tego scripteepolecenia jako zamiennika dla tee.

my-long-running-command | scriptee

Niestety, nie wydaje mi się, aby taka wersja działała idealnie w systemie Linux, więc wydaje się ograniczona do uniksów w stylu BSD.

W systemie Linux jest to blisko, ale nie wyświetla się monit po jego zakończeniu (dopóki nie naciśniesz enter itp.) ...

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

Dlaczego to działa? Czy „skrypt” wyłącza buforowanie?
Aaron Digulla,

@Aaron Digulla: scriptemuluje terminal, więc tak, uważam, że wyłącza buforowanie. Powtarza także echo każdej wysłanej do niej postaci - i dlatego catjest wysyłana /dev/nullw tym przykładzie. Jeśli chodzi o uruchomiony program script, rozmawia on z sesją interaktywną. Myślę, że expectpod tym względem jest podobny , ale scriptprawdopodobnie jest częścią twojego podstawowego systemu.
jwd 7.12.16

Powodem, dla którego używam teejest wysłanie kopii strumienia do pliku. Gdzie jest określony plik scriptee?
Bruno Bronosky

@BrunoBronosky: Masz rację, to zła nazwa tego programu. Tak naprawdę nie wykonuje operacji „tee”. Po prostu wyłącza buforowanie danych wyjściowych, zgodnie z pierwotnym pytaniem. Może powinien to się nazywać „skrypt-kot” (choć nie łączy konkatenacji ...). Niezależnie od tego możesz zastąpić catpolecenie tee myfile.txti powinieneś uzyskać pożądany efekt.
jwd

2

Znalazłem to sprytne rozwiązanie: (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

To załatwia sprawę. catodczyta dodatkowe dane wejściowe (do EOF) i przekaże je do potoku po echoumieszczeniu swoich argumentów w strumieniu wejściowym shell_executable.


2
W rzeczywistości catnie widzi danych wyjściowych echo; po prostu uruchom dwa polecenia w podpowłoce, a dane wyjściowe obu zostaną przesłane do potoku. Drugie polecenie w podpowłoce („cat”) czyta ze standardowego / zewnętrznego standardowego wejścia, dlatego działa.
Aaron Digulla,

0

Zgodnie z tym rozmiar bufora potoku wydaje się być ustawiony w jądrze i wymaga zmiany kompilacji jądra.


7
Uważam, że to inny bufor.
Samuel Edwin Ward
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.