neděle 23. února 2014

Bash - s jednou rourou výstup v terminálu vidím, se dvěma už ne ..

Roury jsem podrobně popsal již dříve, ale přeci jen jsem minul jednu záludnost, která může způsobit bolení hlavy. Problém se týká vícenásobného rourácení, jehož vstupem je stream dat, tedy data proudící v delším časovém rozmezí, která potřebujete prohnat několika programy a přitom v "reálném čase" sledovat výstup. Jedna roura data ukáže, ale když přidáte další, výstup nikde. Který hajzlík za to může? Inteligentní výstupní buffer...



Klasický příklad pro demonstraci tohoto problému je příkaz tail -f, který vypíše posledních 10 řádků určeného souboru a pak vypisuje každý další řádek, který se v souboru objeví, dokud ho neukončíte.

tail -f soubor | grep něco | awk ...

Při použití takovéto roury dochází k tomu, že marně čekáte na výstup awk v terminálu, přitom když ho odstraníte, grep do terminálu normálně vypisuje. Nakonec se výstup v terminálu objeví, třeba až když je příkaz tail ukončen, proto také toto chování nezaznamenáte, když tail použijete bez přepínače -f.

Problém je v bufferu, který je automaticky použit na výstupu grepu, pokud nesměřuje do terminálu. Není to přímo chování samotného grepu, na svědomí to má knihovna libc, přes kterou jsou systémové I/O operace realizovány. Základní velikost bufferu je 4kB a pokud výstup nesměřuje do terminálu, je plně využit. Dokud se nenaplní, nebo nezanikne jejich zdroj, nejdou data dál. Samozřejmě tehdy, pokud samotná aplikace, která knihovnu využívá, chování bufferu nezmění.

Pěkné shrnutí je zde:
http://www.pixelbeat.org/programming/stdio_buffering/
http://mywiki.wooledge.org/BashFAQ/009

Některé programy mají přepínače, umožňující buffer vypnout, či přepnout na řádkový režim, např.:

grep --line-buffered
sed -u / --unbuffered
awk -W interactive - většina implementací, ale není to POSIX, nebo přímo awk funkce flush(), která se nedávno dostala i do POSIXu.
tcpdump -l
tethereal -l

Nenabízí-li samotná aplikace možnost upravit chování výstupního bufferu, jsou tři metody, jak problém obejít.

  • Přepsat skript tak, aby se bufferování neprojevilo - zrušení zbytečné roury, když můžete místo dvou programů použít jeden (awk, sed), případně oddělit filtry do samostatného subshellu pomocí konstrukce tail -f soubor | while read line; do echo $line | filter1 | filter2 | .... ; done, přičemž kumulativní filtry typu uniq, sort, .. je zas třeba umístit přes rouru až za smyčku while/do.
  • Podstrčit falešný terminál mezi výstup aplikace a rouru, takové wrappery také existují
  • Využít proměnné prostředí LD_PRELOAD a podstrčit aplikaci vlastní knihovnu s upravenou funkcí, která buffer nastaví. Využije se dynamického linkování sdílených knihoven a tato proměnná umožňuje k aplikaci natáhnout další knihovnu, která má přednost, když se volá nějaká funkce. To se děje za běhu programu, není třeba upravovat žádné zdrojáky, krom napsání té upravené funkce. Tedy, také to nemusíme psát sami, hotové řešení existuje.

Právě poslední možnost využívá program stdbuf, který se relativně nedávno dostal do coreutils (verze 7.5) a nastavení bufferu umožňuje pohodlně přenastavit. Umí žádné bufferování, nebo řádkové a to jak pro výstup, tak i pro vstup. Ano, stejný buffer je i na vstupu a může s ním být podobná legrace. Náš příklad s nastavením bufferu na řádek pak vypadá následovně:

tail -f logfile | stdbuf -oL grep něco | awk ...

Místo L je možné použít 0 pro vyřazení bufferu úplně, nebo nastavit jeho velikost podle přání. Viz manuál. Pokud budete roury přidávat, musíte použít stdbuf pro každý postižený příkaz, který nemá na konci terminál.

Samozřejmě, že pokud program, jehož buffer potřebujete upravit, využívá nestandardního řešení vstupně výstupních operací a nevyužívá libc, ani s stdbuf uspět nemusíte.

http://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/


Žádné komentáře:

Okomentovat

Zkuste prosím při komentováni používat místo volby Anonymní volbu Název/adresa URL, kde vyplníte nějakou přezdívku, adresu zadávat nemusíte. Vědět, které příspěvky jsou od jednoho člověka, je fajn. Díky.

Pokud by se vám náhodou odeslaný komentář na stránce nezobrazil, vytáhnu ho z koše hned jak si toho všimnu. I Google spam filter se občas sekne.