Mám skript, který volá dva příkazy:
long_running_command | print_progress
The long_running_command
tiskne pokrok, ale jsem z toho nespokojen. Používám print_progress
aby to bylo hezčí (jmenovitě vytisknu postup do jednoho řádku).
Problém: Připojení potrubí ke stdout také aktivuje 4K buffer, takže Nice print program nedostane nic ... nic ... nic ... celek hodně ... :)
Jak mohu zakázat 4K
buffer pro long_running_command
(ne, nemám zdroj)?
Můžete použít příkaz unbuffer
(který je součástí balíčku expect
), např.
unbuffer long_running_command | print_progress
unbuffer
se připojí k long_running_command
přes pseudoterminál (pty), což způsobuje, že systém s ním zachází jako s interaktivním procesem, a proto nepoužívá vyrovnávací paměť 4 kiB v potrubí, což je pravděpodobná příčina zpoždění.
U delších potrubí může být nutné zrušit vyrovnávací paměť každého příkazu (kromě posledního), např.
unbuffer x | unbuffer -p y | z
Dalším způsobem, jak tuto kočku stáhnout, je použít program stdbuf
, který je součástí Coreutils GNU Coreutils (FreeBSD má také svůj vlastní)).
stdbuf -i0 -o0 -e0 command
Tím se zcela vypne ukládání do vyrovnávací paměti pro vstup, výstup a chyby. Pro některé aplikace může být z důvodů výkonu vhodnější ukládání do vyrovnávací paměti:
stdbuf -oL -eL command
Všimněte si, že funguje pouze pro vyrovnávací paměť stdio
(printf()
, fputs()
...) pro dynamicky propojené aplikace a pouze v případě, že tato aplikace jinak neupravuje vyrovnávací paměť svých standardních toků samotných, i když by to mělo zahrnovat většinu aplikací.
Ještě další způsob, jak zapnout výstupní režim vyrovnávací paměti řádku pro long_running_command
je použití příkazu script
, který spustí váš long_running_command
v pseudo terminálu (pty).
script -q /dev/null long_running_command | print_progress # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress # Linux
Pro grep
, sed
a awk
můžete vynutit, aby byl výstup vyrovnáván na řádku. Můžeš použít:
grep --line-buffered
Vynutit výstup do vyrovnávací paměti řádku. Ve výchozím nastavení je výstup ukládán do vyrovnávací paměti, pokud je standardní výstup terminál a blok je ukládán do vyrovnávací paměti.
sed -u
Nastavit vyrovnávací paměť na výstupním řádku.
Více informací naleznete na této stránce: http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html
Pokud je problém s libcem modifikujícím jeho buffering/flushing, když výstup nejde na terminál, měli byste zkusit socat . Můžete vytvořit obousměrný tok mezi téměř jakýmkoli druhem I/O mechanismu. Jedním z nich je rozeklaný program mluvící s pseudotty.
socat EXEC:long_running_command,pty,ctty STDIO
To, co dělá, je
Pokud vám dává stejný výstup jako long_running_command
, pak můžete pokračovat s dýmkou.
Edit: Wow Neviděl unbuffer odpověď! Socat je stejně skvělý nástroj, takže bych mohl tuto odpověď nechat
Můžeš použít
long_running_command 1>&2 |& print_progress
Problém je v tom, že libc bude line-buffer při stdout na obrazovku a full-buffer při stdout do souboru. Ale ne-buffer pro stderr.
Nemyslím si, že je to problém s potrubním bufferem, je to všechno o zásadách libc pro buffer.
Býval to případ a pravděpodobně stále platí, že když je standardní výstup zapsán na terminál, je ve výchozím nastavení vyrovnávací paměť řádku - když je zapsán nový řádek, je linka zapsána na terminál. Když je standardní výstup odeslán do potrubí, je plně vyrovnávací paměť - takže data jsou odeslána do dalšího procesu v potrubí, když je vyplněna standardní vyrovnávací paměť I/O.
To je zdroj problémů. Nejsem si jistý, zda je toho mnoho, co můžete udělat, abyste to napravili, aniž byste upravili zápis programu do kanálu. Můžete použít funkci setvbuf()
s příznakem _IOLBF
A bezpodmínečně uvést stdout
do režimu vyrovnávací paměti řádku. Ale nevidím snadný způsob, jak to vynutit v programu. Nebo program umí fflush()
ve vhodných bodech (po každém řádku výstupu), ale platí stejný komentář.
Předpokládám, že pokud jste nahradili potrubí za pseudo-terminál, pak by standardní I/O knihovna myslela, že výstup byl terminál (protože je to typ terminálu) a automaticky by vyrovnávací paměť linky. To je složitý způsob, jak se s věcmi vypořádat.
Vím, že se jedná o starou otázku, která již měla spoustu odpovědí, ale pokud se chcete vyhnout problému s vyrovnávací pamětí, zkuste něco jako:
stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt
Výsledkem budou protokoly v reálném čase a také jejich uložení do logs.txt
soubor a vyrovnávací paměť již nebudou mít vliv na tail -f
příkaz.
Nemyslím si, že problém je s trubkou. Zní to, že váš dlouhodobě probíhající proces dostatečně často nevypláchne svůj vlastní buffer. Změna velikosti vyrovnávací paměti potrubí by byla hackem, který by ji obešel, ale nemyslím si, že je to možné bez přestavby jádra - něco, co byste nechtěli dělat jako hack, protože to pravděpodobně ovlivňuje mnoho dalších procesů.
Podobně jako odpověď Čada , můžete napsat malý skript, jako je tento:
# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'
Potom použijte tento příkaz scriptee
jako náhradu za tee
.
my-long-running-command | scriptee
Bohužel, nemůžu vypadat, že by taková verze fungovala perfektně v Linuxu, takže se zdá být omezena na unixy ve stylu BSD.
V operačním systému Linux je to blízko, ale výzvu nedostanete zpět, až skončí (dokud nestisknete klávesu Enter atd.) ...
script -q -c 'cat > /proc/self/fd/1' /dev/null
Podle tento příspěvek zde , můžete zkusit zmenšit ulimit potrubí na jeden jediný blok 512 bajtů. Určitě to nevypne ukládání do vyrovnávací paměti, ale dobře, 512 bajtů je mnohem méně než 4K: 3
Našel jsem toto chytré řešení: (echo -e "cmd 1\ncmd 2" && cat) | ./Shell_executable
To dělá trik. cat
přečte další vstup (až do EOF) a předá jej do potrubí poté, co echo
vloží své argumenty do vstupního proudu Shell_executable
.
Podle this je velikost vyrovnávací paměti potrubí nastavena v jádře a bude vyžadovat, abyste změnili jádro.