Wymusza buforowanie linii na stdout podczas podłączania rurociągu do trójnika

118

Zwykle stdoutjest buforowany wierszami. Innymi słowy, dopóki printfargument kończy się znakiem nowej linii, możesz oczekiwać, że zostanie on natychmiast wydrukowany. Wydaje się, że nie działa, gdy używasz potoku do przekierowania tee.

Mam program w C ++ a, który wyprowadza ciągi, zawsze \nzakończone, do stdout.

Gdy jest uruchamiany samodzielnie ( ./a), wszystko jest drukowane poprawnie i we właściwym czasie, zgodnie z oczekiwaniami. Jeśli jednak prześlę to do tee( ./a | tee output.txt), nic nie drukuje, dopóki nie zakończy pracy, co jest sprzeczne z celem użycia tee.

Wiem, że mogłem to naprawić dodając fflush(stdout)po każdej operacji drukowania w programie C ++. Ale czy istnieje czystszy i łatwiejszy sposób? Czy istnieje polecenie, które mogę uruchomić, na przykład, które stdoutwymusiłoby buforowanie liniowe, nawet przy użyciu potoku?

houbysoft
źródło

Odpowiedzi:

67

Wypróbuj, unbufferktóra jest częścią expectpakietu. Być może już masz to w swoim systemie.

W twoim przypadku użyłbyś tego w ten sposób:

./a | unbuffer -p tee output.txt

( -pdotyczy trybu potoku, w którym unbuffer czyta ze standardowego wejścia i przekazuje go do polecenia w pozostałych argumentach)

Wstrzymano do odwołania.
źródło
Dzięki, to zadziałało, chociaż musiałem się skompilować, expectponieważ unbuffernie wydaje się być domyślnie uwzględniony w systemie OS X.
houbysoft
@houbysoft: Cieszę się, że ci się udało. unbufferto tylko mały skrypt, więc nie powinieneś był potrzebować rekompilować całego pakietu.
Wstrzymano do odwołania.
Tak, prawdopodobnie nie, ale ./configure && makezajęło to około 10 sekund, a potem po prostu przeniosłem się unbufferdo /usr/local/bin:)
houbysoft
3
Zainstalowałem go na moim komputerze Mac (10.8.5) przez brew: brew install oczekiwać --with-brewed-tk
Nils
2
FWIW, ponieważ unbuffer jest nieco zagmatwany, odpowiednia struktura jest unbuffer {commands with pipes/tee}.
Fałszywe nazwisko
128

możesz spróbować stdbuf

$ stdbuf -o 0 ./a | tee output.txt

(duża) część strony podręcznika:

  -i, --input=MODE   adjust standard input stream buffering
  -o, --output=MODE  adjust standard output stream buffering
  -e, --error=MODE   adjust standard error stream buffering

If MODE is 'L' the corresponding stream will be line buffered.
This option is invalid with standard input.

If MODE is '0' the corresponding stream will be unbuffered.

Otherwise MODE is a number which may be followed by one of the following:
KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.
In this case the corresponding stream will be fully buffered with the buffer
size set to MODE bytes.

pamiętaj jednak o tym:

NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does
for e.g.) then that will override corresponding settings changed by 'stdbuf'.
Also some filters (like 'dd' and 'cat' etc.) dont use streams for I/O,
and are thus unaffected by 'stdbuf' settings.

nie biegasz stdbufdalej tee, ty go uruchamiasz a, więc nie powinno to mieć na ciebie wpływu, chyba że ustawisz buforowanie astrumieni w aźródle.

Ponadto niestdbuf jest POSIX, ale częścią GNU-coreutils.

c00kiemon5ter
źródło
3
Dzięki, ale to nie wydaje się być dostępne na OS X (pytanie jest oznaczone jako osx-lion).
houbysoft
2
@houbysoft - Jestem prawie pewien, że narzędzia GNU można zainstalować w systemie OS X
jordanm
1
@jordanm: być może, ale instalowanie wszystkich narzędzi GNU wydaje się
przesadą
1
Głosowałem za tą odpowiedzią, ponieważ stdbufjest już dostępna w dystrybucjach Centos Linux, których używamy, i unbuffernie jest. Dzięki!
Huw Walters
6
Dla skryptu Pythona stdbuf nie będzie działać, ale możesz użyć -udo wyłączenia buforowania po stronie Pythona:python3 -u a.py | tee output.txt
Honza
27

Możesz także spróbować wykonać swoje polecenie w pseudoterminalu, używając scriptpolecenia (co powinno wymusić buforowanie linii na potoku)!

script -q /dev/null ./a | tee output.txt     # Mac OS X, FreeBSD
script -c "./a" /dev/null | tee output.txt   # Linux

Należy pamiętać, że scriptpolecenie nie propaguje z powrotem statusu zakończenia opakowanej komendy.

jon
źródło
3
script -t 1 /path/to/outputfile.txt ./adziałał świetnie w moim przypadku użycia. Na żywo przesyła wszystkie dane wyjściowe do, outputfile.txtjednocześnie drukując je na standardowe wyjście powłoki. Nie trzeba było używaćtee
Peter Berg
26

Możesz użyć setlinebuf z stdio.h.

setlinebuf(stdout);

Powinno to zmienić buforowanie na „buforowane liniowo”.

Jeśli potrzebujesz większej elastyczności, możesz użyć setvbuf.

Denys Rtveliashvili
źródło
8
Zastanawiam się, dlaczego to rozwiązanie ma tak mało głosów pozytywnych. To jedyne rozwiązanie, które nie obciąża dzwoniącego.
oxygene
1
Zauważ, że nie jest to standardowe C (ani nawet POSIX). Prawdopodobnie lepiej jest użyć setvbuf(stdout, NULL, _IOLBF, 0), co jest dokładnie równoważne.
rvighne
To rozwiązało mój problem w systemie OS X Catalina z programem w C ++, który był printf () ing i przekierowywałem do tee, ale widziałem dane wyjściowe tylko po zakończeniu programu.
jbaxter
2

Jeśli zamiast tego używasz klas strumienia C ++, każdy std::endl jest niejawnym opróżnieniem. Używając drukowania w stylu C, myślę, że metoda, którą zasugerowałeś ( fflush()), jest jedynym sposobem.

Kevin Grant
źródło
4
Niestety to nieprawda. Możesz zaobserwować to samo zachowanie z c ++ std :: cout, nawet jeśli używasz std :: endl lub std :: flush. Buforowanie odbywa się na wierzchu, a najprostszym rozwiązaniem w Linuksie wydaje się być setlinebuf (stdout); jako pierwsza linia w main (), gdy jesteś autorem programu i korzystasz z innych powyższych rozwiązań, gdy nie możesz zmienić kodu źródłowego.
oxygene
1
@oxygene To nie jest prawda. Wypróbowałem to i endl opróżnia bufor podczas podłączania do trójnika (w przeciwieństwie do printf). Kod: #include <iostream> #include <unistd.h> int main(void) { std::cout << "1" << std::endl; sleep(1); std::cout << "2" << std::endl; }. endl zawsze opróżnia
Curtis Yallop,
0

unbufferKomenda z expectpakietu na @Paused aż do wypowiedzenia dalszego odpowiedź nie pracował dla mnie sposób, w jaki został przedstawiony.

Zamiast używać:

./a | unbuffer -p tee output.txt

Musiałem użyć:

unbuffer -p ./a | tee output.txt

( -pdotyczy trybu potoku, w którym unbuffer czyta ze standardowego wejścia i przekazuje go do polecenia w pozostałych argumentach)

expectPakiet można zainstalować na:

  1. MSYS2 z pacman -S expected
  2. Mac OS z brew install expected
użytkownik
źródło