Напишіть Python stdout, щоб подати файл негайно


51

При спробі записати stdout із сценарію Python у текстовий файл ( python script.py > log), текстовий файл створюється при запуску команди, але власне вміст не записується до завершення сценарію Python. Наприклад:

script.py:

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

друкує до stdout кожні 5 секунд при python script.pyдзвінку з , але коли я дзвоню python script.py > log, розмір файлу журналу залишається нульовим, поки сценарій не завершиться. Чи можна безпосередньо записати у файл журналу, щоб ви могли стежити за ходом сценарію (наприклад, використовуючи tail)?

EDIT Виявляється, що python -u script.pyце хитрість, я не знав про буферизацію stdout.


1
@jezmck, я міг зрозуміти питання неправильно.
zyxue

Відповіді:


64

Це відбувається тому, що зазвичай, коли процес STDOUT переспрямовується на щось інше, ніж на термінал, тоді вихід виводиться в буфер розміром з конкретним ОС (можливо, у багатьох випадках 4 к або 8 к). І навпаки, при виході на термінал STDOUT буде буферизований або взагалі не буферизований, тому ви побачите вихід після кожного \nабо для кожного символу.

Загалом ви можете змінити буферизацію STDOUT за допомогою stdbufутиліти:

stdbuf -oL python script.py > log

Тепер, якщо ви tail -F log, ви повинні побачити кожен рядок виводу, як тільки він генерується.


Альтернативно, явне промивання вихідного потоку після кожного друку повинно досягати того ж. Схоже, цього sys.stdout.flush()слід досягти в Python. Якщо ви використовуєте Python 3.3 або більш пізню версію, то printфункція також має flushключове слово , яке робить це: print('hello', flush=True).


8
Дякую, я не знав про буферизацію! Знаючи це, Google досить швидко сказав мені, що python -u script.pyробить свою справу. РЕДАКЦІЯ Стільки відповідей одразу, я прийняв вашу, оскільки вона спрямовувала мене у напрямку буферизації.
Барт

1
@julbra Класно, так, я теж не знав, що у python був такий варіант. Деякі програми командного рядка також мають подібні параметри - наприклад, --line-bufferedдля grepдеяких, але інші - не. stdbufє загальною утилітою catchall для боротьби з тими, хто цього не робить.
Цифрова травма

@DigitalTrauma: Чи не краще взагалі не використовувати буферизацію, тобто stdbuf -o0 python script.py > logв таких визначених обставинах?
heemayl

@heemayl -oL- це компроміс. В основному більші буфери забезпечуватимуть кращу ефективність при перенаправленні кудись (менше системних викликів і менше операцій вводу / виводу). Однак якщо абсолютно необхідно бачити кожного символу як його виведення, то так, -o0знадобиться.
Цифрова травма

@Paul Будь ласка, уникайте копіювання вмісту між відповідями або хоча б згадуйте оригінальних авторів, які надали вміст.
Бакуріу

44

Це має зробити цю роботу:

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

Оскільки Python буде буферувати stdoutза замовчуванням, тут я використовував sys.stdout.flush()для промивання буфера.

Іншим рішенням буде використання -uперемикача (небуферизованого) python. Отже, зробимо і наступне:

python -u script.py >> log

11

Варіацією теми використання власного варіанту python для нерозподіленого виводу було б використання #!/usr/bin/python -uв якості першого рядка.

З #!/usr/bin/env pythonцим додатковим аргументом не вдасться працювати, тому, як альтернатива, можна запустити PYTHONUNBUFFERED=1 ./my_scriipt.py > output.txtабо зробити це в два етапи:

$ export PYTHONUNBUFFERED=1
$ ./myscript.py

10

Вам слід перейти flush=Trueдо printфункції:

import time

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

Згідно з документацією, за замовчуванням printнічого про примивання не застосовується:

Буферизація виводу зазвичай визначається файлом, але якщо flushаргумент ключового слова є істинним, потік примусово промивається.

А в документації на sysпотоки 's:

Коли інтерактивні, стандартні потоки буферні. Інакше вони блокуються як звичайні текстові файли. Ви можете змінити це значення за допомогою параметра -uкомандного рядка.


Якщо ви застрягли з давньою версією python, вам потрібно викликати flushметод sys.stdoutпотоку:

import sys
import time

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

1
Аргумент flush = True добре працює з Python 3.4.2, насправді не працює з давнім (..) Python 2.7.9
Bart

Ця відповідь говорить про те саме, що було DigitalTraumaсказано за 10 годин до цього. Ви повинні відхилити його посаду, а не повторювати те ж саме.
dotancohen

4
@dotancohen Насправді частина про print(flush=True)цю відповідь була додана стороннім автором після моєї відповіді. Мені здається поганим смаком вирвати вміст з моєї відповіді, щоб передати їх іншому без кредиту. Я вирішив додати свою відповідь виключно тому, що жодна відповідь не містила жодної згадки про найпростіший спосіб досягти того, що ОП хотів у нових версіях python, і я додав "старий шлях" лише для повноти. Наступного разу, будь ласка, перевірте історію редагування, перш ніж коментувати та відхиляти звук.
Бакуріу

@Bakuriu: Тоді мені шкода! Це показує вагому причину завжди публікувати, чому при зволіканні . Чи можете ви, будь ласка, трохи відредагувати публікацію, щоб я міг змінити свою заяву на позицію? Дякую!
dotancohen

Він повинен працювати з Python 2.7 , якщо ви робите __future__імпорт: from __future__ import print_function. Але так, це для сумісності лише з Python 3
Сергій Колодяжний
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.