В python, как мне проверить стандартный вывод из объекта subprocess.Popen на наличие чего-либо для чтения?

В python, как мне проверить стандартный вывод из объекта subprocess.Popen на наличие чего-либо для чтения? Я пишу оболочку для инструмента, который иногда работает часами подряд. Использование .readline() в стандартном выводе из дочернего процесса сильно снижает скорость скрипта, если он выполняется дольше нескольких минут. Мне нужен способ более эффективно проверять стандартный вывод, если есть что читать. Кстати, этот конкретный инструмент записывает только полные строки за раз. Сценарий выглядит следующим образом:

    #!/usr/bin/python -u
    #thiswrap.py

    import sys, time
    from subprocess import *

    chldp = Popen(sys.argv[1], bufsize=0, stdout=PIPE, close_fds=True)
    chstdin,chstdout=chldp.stdin,chldp.stdout
    startnoti=False

    while not chldp.poll():
        rrl=chstdout.readline() # <--- this is where the problem is
        if rrl[-8:]=='REDACTED TEXT':
            sys.stdout.write(rrl[:-1]+'   \r')
            if not startnoti: startnoti=True
        else:
            if startnoti: sys.stdout.write('\n')
            sys.stdout.write(rrl)
            if startnoti: # REDACTED
            time.sleep(0.1)
        time.sleep(0.1)

Любые идеи?


person Matthew Jensen    schedule 08.08.2011    source источник
comment
Почему проблематично позволить readline заблокировать? А почему ты звонишь sleep?   -  person Vebjorn Ljosa    schedule 08.08.2011
comment
Я собираюсь проигнорировать часть тролля о блокировке строки чтения, и сон на самом деле просто временная мера, пока я не смогу решить проблему чтения строки. Я знаю, что это немного лениво и неуклюже, но мне не понадобится что-то еще в этой части кода, если только это не что-то, что может исходить от лучшего решения, чтобы знать, когда использовать readline(), поэтому он остается там, пока эта проблема не исчезнет прочь.   -  person Matthew Jensen    schedule 10.08.2011


Ответы (3)


Вам нужно установить неблокирующие файловые дескрипторы, вы можете сделать это, используя fcntl :

import sys, time, fcntl, os
from subprocess import *

chldp = Popen(sys.argv[1], bufsize=0, stdout=PIPE, close_fds=True)
chstdin, chstdout = chldp.stdin, chldp.stdout
fl = fcntl.fcntl(chstdout, fcntl.F_GETFL)
fcntl.fcntl(chstdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)

while chldp.poll() is not None:
    try:
        rrl = chstdout.readline()
    except IOError:
        time.sleep(0.1)
        continue
    # use rrl

Когда нет доступных данных, IOError будет увеличено на readline().

Обратите внимание, что, поскольку chldp.poll() может вернуть 0 по завершении подпроцесса, вам, вероятно, следует использовать childp.poll() is not None в вашем while, а не not childp.poll().

person Andrew Clark    schedule 08.08.2011
comment
Просто примечание: это не работает в Windows (я знаю, что постер, похоже, использует Linux), см. stackoverflow.com/questions/375427/ - person agf; 09.08.2011
comment
Это решение не работает. Это приводит к тому, что оболочка не только пропускает строки, но и в любом случае выдает IOErrors. Также вещь неизбежно выходит из строя в течение нескольких минут после запуска. (Кстати, не Linux, mac os 10.6, лол). Кроме того, дочерний процесс никогда не завершится сам по себе, поэтому я сохраняю цикл while как есть. - person Matthew Jensen; 10.08.2011

К сожалению, нет готового способа опроса для условия "в канале достаточно данных с разрывом строки, чтобы readline() немедленно вернулся".

Если вам нужна строка за раз и вы не хотите блокировать, вы также можете:

Либо реализуйте свою собственную буферизацию через класс или генератор и опросите его, например:

def linereader():
    data = ""
    while True:
        if poll(f.fd):
            data += f.read(100)
        lines = data.split("\n")
        data = lines[-1]
        for line in lines[:-1]:
            yield line

# use
for line in linereader():
    if line:
       print line
    else:
       time.sleep(...)

Или используйте потоки (оставленные читателю в качестве упражнения, обратите внимание, что в более старых версиях python возникает ошибка, если вы запускаете подпроцесс из потока, отличного от основного)

person Dima Tisnek    schedule 06.02.2012

Предложенное решение в первом комментарии почти правильное. Вам просто нужно передать целочисленный файловый дескриптор в качестве первого аргумента fcntl.fcntl, а не файловый объект Python. Взял из другой ответ

Вот код, который нужно изменить:

chstdout = chldp.stdout
fd = chstdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
person krydev    schedule 21.07.2021