Natychmiast zapisz stdout do pliku

51

Podczas próby zapisania standardowego skryptu ze skryptu w języku Python do pliku tekstowego ( python script.py > log) plik tekstowy jest tworzony po uruchomieniu polecenia, ale rzeczywista zawartość nie jest zapisywana, dopóki skrypt się nie zakończy. Na przykład:

script.py:

import time
for i in range(10):
    print('bla')
    time.sleep(5)

wypisuje na standardowe wyjście co 5 sekund po wywołaniu python script.py, ale kiedy dzwonię python script.py > log, rozmiar pliku dziennika pozostaje zerowy do momentu zakończenia skryptu. Czy jest możliwe bezpośrednie zapisanie do pliku dziennika, abyś mógł śledzić postęp skryptu (np. Używając tail)?

EDYCJA Okazuje się, że to python -u script.pydziała, nie wiedziałem o buforowaniu standardowego wyjścia.

Bart
źródło
1
@jezmck, mogłem źle zrozumieć pytanie.
zyxue

Odpowiedzi:

64

Dzieje się tak, ponieważ normalnie, gdy proces STDOUT jest przekierowywany na coś innego niż terminal, wówczas dane wyjściowe są buforowane w buforze o rozmiarze specyficznym dla systemu operacyjnego (być może 4k lub 8k w wielu przypadkach). I odwrotnie, podczas wysyłania do terminala STDOUT będzie buforowany liniowo lub nie będzie buforowany, więc zobaczysz wynik po \nkażdym znaku lub dla każdego znaku.

Ogólnie można zmienić buforowanie STDOUT za pomocą stdbufnarzędzia:

stdbuf -oL python script.py > log

Teraz, jeśli tail -F logpowinieneś, powinieneś zobaczyć wyjście każdego wiersza natychmiast po jego wygenerowaniu.


Alternatywnie jawne opróżnianie strumienia wyjściowego po każdym wydruku powinno osiągnąć to samo. Wygląda na to, że sys.stdout.flush()powinno to zostać osiągnięte w Pythonie. Jeśli używasz Pythona 3.3 lub nowszy, printfunkcja ma również flushsłowa kluczowego, które wykonuje to: print('hello', flush=True).

Cyfrowa trauma
źródło
8
Dzięki, nie wiedziałem o buforowaniu! Wiedząc o tym, Google dość szybko powiedział mi, że python -u script.pyto załatwia sprawę. EDYTUJ Tak wiele odpowiedzi na raz, zaakceptowałem twoją, ponieważ wskazała mi kierunek buforowania.
Bart
1
@julbra Cool, tak, nie wiedziałem, że python też miał tę opcję. Niektóre programy wiersza polecenia mają również podobne opcje - np. --line-bufferedDla grep, ale inne nie. stdbufjest ogólnym narzędziem catchall do radzenia sobie z tymi, które tego nie robią.
Cyfrowa trauma
@DigitalTrauma: Czy nie lepiej jest w ogóle nie używać buforowania, tj. stdbuf -o0 python script.py > logW takich określonych okolicznościach?
heemayl
@heemayl -oLto kompromis. Zasadniczo większe bufory zapewnią lepszą wydajność podczas przekierowania (mniej wywołań systemowych i mniej operacji we / wy). Jeśli jednak bezwzględnie konieczne jest, aby zobaczyć każdy znak w postaci wyjściowej, wówczas tak, -o0byłoby wymagane.
Cyfrowa trauma
@Paul Unikaj kopiowania treści między odpowiedziami lub przynajmniej wymień oryginalnych autorów, którzy dostarczyli treść.
Bakuriu
44

To powinno wykonać zadanie:

import time, sys
for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)

Ponieważ Python buforuje stdoutdomyślnie, tutaj użyłem sys.stdout.flush()do opróżnienia bufora.

Innym rozwiązaniem byłoby użycie -u(niebuforowanego) przełącznika python. Tak więc zrobią to również:

python -u script.py >> log
heemayl
źródło
11

Wariacją na temat używania własnej opcji Pythona dla niebuforowanych danych wyjściowych byłoby użycie #!/usr/bin/python -ujako pierwszego wiersza.

Z #!/usr/bin/env pythontym dodatkowym argumentem nie zadziała, więc alternatywnie można uruchomić PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txtlub zrobić to w dwóch krokach:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py
Sergiy Kolodyazhnyy
źródło
10

Powinieneś przejść flush=Truedo printfunkcji:

import time

for i in range(10):
    print('bla', flush=True)
    time.sleep(5)

Zgodnie z dokumentacją domyślnie printnie wymusza niczego na opróżnianie:

To, czy dane wyjściowe są buforowane, jest zwykle określane przez plik, ale jeśli flushargument słowa kluczowego jest prawdziwy, strumień jest wymuszany.

Dokumentacja sysstrumieni mówi:

W trybie interaktywnym standardowe strumienie są buforowane w linii. W przeciwnym razie są buforowane blokowo jak zwykłe pliki tekstowe. Możesz zastąpić tę wartość za pomocą -uopcji wiersza polecenia.


Jeśli utkniesz w starożytnej wersji Pythona, musisz wywołać flushmetodę sys.stdoutstrumienia:

import sys
import time

for i in range(10):
    print('bla')
    sys.stdout.flush()
    time.sleep(5)
Bakuriu
źródło
1
Argument flush = True działa dobrze w Pythonie 3.4.2, w rzeczywistości nie działa w starożytnym (..) Pythonie 2.7.9
Bart
Ta odpowiedź sugeruje to samo, co DigitalTraumapowiedziane 10 godzin wcześniej. Powinieneś zagłosować za jego postem, a nie znowu to samo.
dotancohen
4
@dotancohen Właściwie część print(flush=True)została dodana do tej odpowiedzi po mojej przez autora zewnętrznego. Uważam, że to zły gust, aby zrywać treści z mojej odpowiedzi i umieszczać je w innym, bez uznania. Postanowiłem dodać moją odpowiedź wyłącznie dlatego, że żadne odpowiedzi nie zawierały wzmianki o najprostszym sposobie osiągnięcia tego, czego OP chciał w nowszych wersjach Pythona, i dodałem „starą drogę” dla kompletności. Następnym razem sprawdź historię zmian przed komentowaniem i / lub oddaniem głosu.
Bakuriu
@Bakuriu: Przepraszam! To pokazuje dobry powód, aby zawsze publikować pytanie, dlaczego podczas głosowania w dół . Czy mógłbyś trochę edytować post, abym mógł zmienić zdanie negatywne na pozytywne? Dziękuję Ci!
dotancohen
Powinien działać z Pythona 2.7, jeśli nie __future__import: from __future__ import print_function. Ale tak, to tylko dla zgodności z Pythonem 3
Sergiy Kolodyazhnyy