Подпроцесс опроса завершен при циклировании стандартного вывода

#python #python-3.x #subprocess

#python #python-3.x #подпроцесс

Вопрос:

Я пишу скрипт, который выдает выходные данные непредсказуемого размера, я хочу знать изнутри цикла, когда скрипт завершится.

Это код:

 #!/usr/bin/env python3
import subprocess
import shlex

def main():
    cmd = 'bash -c "for i in $(seq 1 15);do echo $i ;sleep 1;done"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE,
                         universal_newlines=True)
    for line in p.stdout:
        print(f"file_name: {line.strip()}")
        print(p.poll())

if __name__ == "__main__":
    main()
 

p.poll() Всегда None четный на последней итерации, и это имеет смысл, потому что после echo этого sleep он длится 1 секунду, прежде чем перейти к следующей итерации и завершается.

Любой способ заставить его работать?

Комментарии:

1. После цикла: p.wait() и print(p.poll()) должно сделать свое дело.

2. @zvone конечно, но я хочу посмотреть, смогу ли я сделать это изнутри цикла.

Ответ №1:

Вы уже определили проблему, то есть после того, как подпроцесс отправил последнюю строку, он все равно будет продолжать выполняться в течение одной секунды, и поэтому, пока программа находится в цикле, программа всегда будет считаться запущенной. Даже если вы переместите вызов за poll пределы цикла, вам, возможно, придется немного подождать, чтобы дать подпроцессу возможность завершиться после вывода его окончательного сообщения (я уменьшил размер цикла — жизнь слишком коротка):

 #!/usr/bin/env python3
import subprocess
import shlex
import time

def main():
    cmd = 'bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, universal_newlines=True)
    for line in p.stdout:
        print(f"file_name: {line.strip()}", flush=True)
    print(p.poll())
    time.sleep(.1)
    print(p.poll())

if __name__ == "__main__":
    main()
 

С принтами:

 bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"
file_name: 1
file_name: 2
file_name: 3
file_name: 4
file_name: 5
None
0
 

Чтобы «заставить его работать» внутри цикла, потребуются специальные знания о том, что происходит внутри подпроцесса. Основываясь на предыдущем фрагменте кода, нам потребуется:

 #!/usr/bin/env python3
import subprocess
import shlex
import time

def main():
    cmd = 'bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, universal_newlines=True)
    for line in p.stdout:
        # has to be greater than the sleep time in the subprocess to give the subprocess a chance to terminate
        print(f"file_name: {line.strip()}", flush=True)
        time.sleep(1.1)
        print(p.poll())

if __name__ == "__main__":
    main()
 

С принтами:

 bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"
file_name: 1
None
file_name: 2
None
file_name: 3
None
file_name: 4
None
file_name: 5
0
 

Но это вряд ли практическое решение. Нужно было бы спросить, в чем причина выполнения этого опроса; он не дает никакой полезной информации, если вы не хотите включать sleep вызовы после ваших чтений, потому что всегда будет некоторая задержка после последней записи, выполняемой подпроцессом, и ее завершения, и эти sleep вызовы, как правило, расточительны. Вы должны просто читать, пока больше не будет вывода, а затем выполнить a p.wait() , чтобы дождаться завершения подпроцесса, но это ваш выбор:

 #!/usr/bin/env python3
import subprocess
import shlex

def main():
    cmd = 'bash -c "for i in $(seq 1 5);do echo $i; sleep 1; done;"'
    print(cmd)
    p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, universal_newlines=True)
    for line in p.stdout:
        print(f"file_name: {line.strip()}", flush=True)
    p.wait()

if __name__ == "__main__":
    main()
 

Комментарии:

1. Насколько я понимаю, мой случай сложный, я принимаю ответ. Спасибо за подробное объяснение.