Неблокирующее чтение на подпроцесс. PIPE в Python - PullRequest
462 голосов
/ 17 декабря 2008

Я использую модуль подпроцесса , чтобы запустить подпроцесс и подключиться к его выходному потоку (stdout). Я хочу иметь возможность выполнять неблокирующие чтения на своем стандартном выводе. Есть ли способ сделать .readline неблокирующим или проверить, есть ли данные в потоке, прежде чем я вызову .readline? Я хотел бы, чтобы это было переносимо или, по крайней мере, работало под Windows и Linux.

вот как я делаю это сейчас (блокировка на .readline, если нет доступных данных):

p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE)
output_str = p.stdout.readline()

Ответы [ 27 ]

0 голосов
/ 25 марта 2017

Это пример запуска интерактивной команды в подпроцессе, и стандартный вывод является интерактивным с использованием псевдотерминала. Вы можете сослаться на: https://stackoverflow.com/a/43012138/3555925

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import select
import termios
import tty
import pty
from subprocess import Popen

command = 'bash'
# command = 'docker run -it --rm centos /bin/bash'.split()

# save original tty setting then set it to raw mode
old_tty = termios.tcgetattr(sys.stdin)
tty.setraw(sys.stdin.fileno())

# open pseudo-terminal to interact with subprocess
master_fd, slave_fd = pty.openpty()

# use os.setsid() make it run in a new process group, or bash job control will not be enabled
p = Popen(command,
          preexec_fn=os.setsid,
          stdin=slave_fd,
          stdout=slave_fd,
          stderr=slave_fd,
          universal_newlines=True)

while p.poll() is None:
    r, w, e = select.select([sys.stdin, master_fd], [], [])
    if sys.stdin in r:
        d = os.read(sys.stdin.fileno(), 10240)
        os.write(master_fd, d)
    elif master_fd in r:
        o = os.read(master_fd, 10240)
        if o:
            os.write(sys.stdout.fileno(), o)

# restore tty settings back
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
0 голосов
/ 28 апреля 2013

Я создал библиотеку на основе J. Решение Ф. Себастьяна . Вы можете использовать его.

https://github.com/cenkalti/what

0 голосов
/ 17 января 2016

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

https://pypi.python.org/pypi/python-nonblock

Предоставляет функцию,

nonblock_read, который будет считывать данные из потока, если он доступен, в противном случае возвращает пустую строку (или None, если поток закрыт с другой стороны и все возможные данные были прочитаны)

Вы также можете рассмотреть модуль python-subprocess2,

https://pypi.python.org/pypi/python-subprocess2

, который добавляет к модулю подпроцесса. Поэтому к объекту, возвращаемому из «subprocess.Popen», добавляется дополнительный метод, runInBackground. Это запускает поток и возвращает объект, который будет автоматически заполняться при записи материала в stdout / stderr, без блокировки вашего основного потока.

Наслаждайтесь!

0 голосов
/ 11 января 2015

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

Вот моя реализация https://gist.github.com/grubberr/5501e1a9760c3eab5e0a он не поддерживает Windows (опрос), не обрабатывает EOF, но у меня это хорошо работает

0 голосов
/ 07 мая 2013

EDIT: эта реализация все еще блокирует. Вместо этого используйте ответ Дж. Ф. Себастьяна .

Я пытался ответить top , но дополнительный риск и обслуживание кода потока вызывали беспокойство.

Просматривая модуль io (и ограниченный 2,6), я нашел BufferedReader. Это мое решение без блокировки, без блокировки.

import io
from subprocess import PIPE, Popen

p = Popen(['myprogram.exe'], stdout=PIPE)

SLEEP_DELAY = 0.001

# Create an io.BufferedReader on the file descriptor for stdout
with io.open(p.stdout.fileno(), 'rb', closefd=False) as buffer:
  while p.poll() == None:
      time.sleep(SLEEP_DELAY)
      while '\n' in bufferedStdout.peek(bufferedStdout.buffer_size):
          line = buffer.readline()
          # do stuff with the line

  # Handle any remaining output after the process has ended
  while buffer.peek():
    line = buffer.readline()
    # do stuff with the line
0 голосов
/ 09 мая 2019

В современном Python дела обстоят намного лучше.

Вот простая дочерняя программа "hello.py":

#!/usr/bin/env python3

while True:
    i = input()
    if i == "quit":
        break
    print(f"hello {i}")

И программа для взаимодействия с ним:

import asyncio


async def main():
    proc = await asyncio.subprocess.create_subprocess_exec(
        "./hello.py", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE
    )
    proc.stdin.write(b"bob\n")
    print(await proc.stdout.read(1024))
    proc.stdin.write(b"alice\n")
    print(await proc.stdout.read(1024))
    proc.stdin.write(b"quit\n")
    await proc.wait()


asyncio.run(main())

Это распечатывает:

b'hello bob\n'
b'hello alice\n'

Обратите внимание, что фактический шаблон, который также содержится почти во всех предыдущих ответах, как здесь, так и в связанных вопросах, состоит в том, чтобы установить дескриптор файла stdout ребенка на неблокирование, а затем опрашивать его в каком-то цикле выбора. В эти дни, конечно, этот цикл предоставляется asyncio.

0 голосов
/ 31 октября 2013

Основываясь на ответе Дж. Ф. Себастьяна и нескольких других источниках, я собрал простой менеджер подпроцессов. Он обеспечивает запрос неблокируемого чтения, а также запускает несколько процессов параллельно. Он не использует специфичные для ОС вызовы (которые мне известны) и поэтому должен работать где угодно.

Он доступен из pypi, так что просто pip install shelljob. Обратитесь к странице проекта с примерами и полными документами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...