Không chặn đọc trên một quy trình con.PIPE trong python


506

Tôi đang sử dụng mô đun quy trình con để bắt đầu một quy trình con và kết nối với luồng đầu ra của nó (thiết bị xuất chuẩn). Tôi muốn có thể thực hiện các lần đọc không chặn trên thiết bị xuất chuẩn của nó. Có cách nào để làm cho .readline không chặn hoặc kiểm tra xem có dữ liệu trên luồng trước khi tôi gọi .readlinekhông? Tôi muốn cái này có thể mang theo hoặc ít nhất là hoạt động trong Windows và Linux.

Bây giờ đây là cách tôi làm điều đó (Nó đang chặn .readlinenếu không có dữ liệu):

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

14
(Đến từ google?) Tất cả các PIPE sẽ bế tắc khi một trong các bộ đệm của PIPE bị đầy và không đọc được. ví dụ bế tắc stdout khi stderr được lấp đầy. Không bao giờ vượt qua PIPE mà bạn không có ý định đọc.
Nasser Al-Wohaibi

@ NasserAl-Wohaibi điều này có nghĩa là tốt hơn để luôn luôn tạo các tệp sau đó?
Charlie Parker

một cái gì đó tôi đã tò mò muốn hiểu là tại sao nó lại bị chặn ngay từ đầu ... Tôi đang hỏi bởi vì tôi đã thấy nhận xét:To avoid deadlocks: careful to: add \n to output, flush output, use readline() rather than read()
Charlie Parker

Đó là, "theo thiết kế", đang chờ để nhận đầu vào.
Mathieu Pagé

Câu trả lời:


403

fcntl, select, asyncprocSẽ không giúp đỡ trong trường hợp này.

Một cách đáng tin cậy để đọc một luồng mà không chặn bất kể hệ điều hành là sử dụng Queue.get_nowait():

import sys
from subprocess import PIPE, Popen
from threading  import Thread

try:
    from queue import Queue, Empty
except ImportError:
    from Queue import Queue, Empty  # python 2.x

ON_POSIX = 'posix' in sys.builtin_module_names

def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()

p = Popen(['myprogram.exe'], stdout=PIPE, bufsize=1, close_fds=ON_POSIX)
q = Queue()
t = Thread(target=enqueue_output, args=(p.stdout, q))
t.daemon = True # thread dies with the program
t.start()

# ... do other things here

# read line without blocking
try:  line = q.get_nowait() # or q.get(timeout=.1)
except Empty:
    print('no output yet')
else: # got line
    # ... do something with line

6
Có điều này làm việc cho tôi, tôi đã loại bỏ rất nhiều mặc dù. Nó bao gồm các thực hành tốt nhưng không phải lúc nào cũng cần thiết. Python 3.x 2.X compat và close_fds có thể bị bỏ qua, nó vẫn hoạt động. Nhưng chỉ cần lưu ý về những gì mọi thứ làm và đừng sao chép nó một cách mù quáng, ngay cả khi nó chỉ hoạt động! (Trên thực tế, giải pháp đơn giản nhất là sử dụng một chủ đề và thực hiện việc đọc như đã làm, Qeues chỉ là một cách dễ dàng để lấy dữ liệu, có những cách khác, các chủ đề là câu trả lời!)
Aki

3
Bên trong chuỗi, lệnh gọi out.readlinechặn luồng và luồng chính, và tôi phải đợi cho đến khi đường đọc trở lại trước khi mọi thứ khác tiếp tục. Bất kỳ cách dễ dàng xung quanh đó? (Tôi đang đọc nhiều dòng từ quy trình của mình, đây cũng là một tệp .py khác đang thực hiện DB và mọi thứ)
Justin

3
@Justin: 'out.readline' không chặn luồng chính mà nó được thực thi trong luồng khác.
jfs

4
Điều gì xảy ra nếu tôi không tắt quy trình con, vd. do ngoại lệ? luồng đọc độc lập sẽ không chết và python sẽ bị treo, ngay cả khi luồng chính đã thoát, phải không? Làm thế nào một người có thể làm việc xung quanh này? python 2.x không hỗ trợ tiêu diệt các chủ đề, điều tồi tệ hơn, không hỗ trợ làm gián đoạn chúng. :( (rõ ràng người ta nên xử lý các ngoại lệ để đảm bảo quy trình con bị tắt, nhưng chỉ trong trường hợp nó không hoạt động, bạn có thể làm gì?)
n611x007

3
Tôi đã tạo một số trình bao bọc thân thiện này trong gói shelljob pypi.python.org/pypi/shelljob
edA-qa mort-ora-y

77

Tôi thường có một vấn đề tương tự; Các chương trình Python tôi viết thường xuyên cần có khả năng thực thi một số chức năng chính đồng thời chấp nhận đầu vào của người dùng từ dòng lệnh (stdin). Đơn giản chỉ cần đặt chức năng xử lý đầu vào của người dùng trong một luồng khác sẽ không giải quyết được vấn đề vì readline()các khối và không có thời gian chờ. Nếu chức năng chính đã hoàn tất và không còn phải chờ thêm người dùng nhập nữa, tôi thường muốn chương trình của mình thoát, nhưng nó không thể vì readline()vẫn đang chặn trong luồng khác đang chờ một dòng. Một giải pháp tôi đã tìm thấy cho vấn đề này là biến stdin thành một tệp không chặn bằng mô-đun fcntl:

import fcntl
import os
import sys

# make stdin a non-blocking file
fd = sys.stdin.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

# user input handling thread
while mainThreadIsRunning:
      try: input = sys.stdin.readline()
      except: continue
      handleInput(input)

Theo tôi, điều này sạch hơn một chút so với việc sử dụng các mô-đun chọn hoặc tín hiệu để giải quyết vấn đề này nhưng sau đó nó chỉ hoạt động trên UNIX ...


1
Theo các tài liệu, fcntl () có thể nhận được một bộ mô tả tệp hoặc một đối tượng có phương thức .fileno ().
Denilson Sá Maia

10
Câu trả lời của Jesse là không chính xác. Theo Guido, readline không hoạt động chính xác với chế độ không chặn và nó sẽ không hoạt động trước Python 3000. bug.python.org/su1175#msg56041 Nếu bạn muốn sử dụng fcntl để đặt tệp ở chế độ không chặn, bạn phải sử dụng os.read () cấp thấp hơn và tự tách ra các dòng. Trộn fcntl với các cuộc gọi cấp cao thực hiện đệm dòng là yêu cầu sự cố.
anonnn

2
Việc sử dụng readline dường như không chính xác trong Python 2. Xem câu trả lời stackoverflow.com/questions/375427/
Catalin Iacob

10
Xin vui lòng, không sử dụng các vòng lặp bận rộn. Sử dụng thăm dò ý kiến ​​() với thời gian chờ để chờ dữ liệu.
Ivo Danihelka

@Stefano buffer_sizeđịnh nghĩa là gì?
con mèo

39

Python 3.4 giới thiệu API tạm thời mới cho asynciomô-đun IO không đồng bộ .

Cách tiếp cận tương tự như twistedcâu trả lời dựa trên @Bryan Ward - xác định một giao thức và các phương thức của nó được gọi ngay khi dữ liệu sẵn sàng:

#!/usr/bin/env python3
import asyncio
import os

class SubprocessProtocol(asyncio.SubprocessProtocol):
    def pipe_data_received(self, fd, data):
        if fd == 1: # got stdout data (bytes)
            print(data)

    def connection_lost(self, exc):
        loop.stop() # end loop.run_forever()

if os.name == 'nt':
    loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(loop.subprocess_exec(SubprocessProtocol, 
        "myprogram.exe", "arg1", "arg2"))
    loop.run_forever()
finally:
    loop.close()

Xem "Quy trình con" trong tài liệu .

Có một giao diện cấp cao asyncio.create_subprocess_exec()trả về Processcác đối tượng cho phép đọc một dòng không đồng bộ bằng cách sử dụng StreamReader.readline()coroutine (với cú pháp async/ awaitPython 3.5+ ):

#!/usr/bin/env python3.5
import asyncio
import locale
import sys
from asyncio.subprocess import PIPE
from contextlib import closing

async def readline_and_kill(*args):
    # start child process
    process = await asyncio.create_subprocess_exec(*args, stdout=PIPE)

    # read line (sequence of bytes ending with b'\n') asynchronously
    async for line in process.stdout:
        print("got line:", line.decode(locale.getpreferredencoding(False)))
        break
    process.kill()
    return await process.wait() # wait for the child process to exit


if sys.platform == "win32":
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
else:
    loop = asyncio.get_event_loop()

with closing(loop):
    sys.exit(loop.run_until_complete(readline_and_kill(
        "myprogram.exe", "arg1", "arg2")))

readline_and_kill() thực hiện các nhiệm vụ sau:

  • bắt đầu quá trình con, chuyển hướng thiết bị xuất chuẩn của nó sang một đường ống
  • đọc một dòng từ thiết bị xuất chuẩn không đồng bộ
  • giết quá trình con
  • chờ nó thoát ra

Mỗi bước có thể được giới hạn bởi thời gian chờ giây nếu cần thiết.


Khi tôi thử một cái gì đó như thế này bằng cách sử dụng python 3,4 coroutines, tôi chỉ nhận được đầu ra khi toàn bộ tập lệnh đã chạy. Tôi muốn thấy một dòng đầu ra được in, ngay khi quy trình con in một dòng. Đây là những gì tôi đã có: pastebin.com/qPssFGep .
flutefreak7

1
@ flutefreak7: vấn đề đệm không liên quan đến câu hỏi hiện tại. Theo liên kết cho các giải pháp có thể.
jfs

cảm ơn! Đã giải quyết vấn đề cho tập lệnh của tôi bằng cách sử dụng đơn giản print(text, flush=True)để văn bản in sẽ có sẵn ngay lập tức cho người xem readline. Khi tôi thử nghiệm nó với chương trình thực thi dựa trên Fortran, tôi thực sự muốn bọc / xem, nó không đệm đầu ra của nó, vì vậy nó hoạt động như mong đợi.
flutefreak7

Có thể cho phép quá trình con tồn tại và thực hiện các hoạt động đọc / ghi tiếp theo. readline_and_kill, trong tập lệnh thứ hai của bạn, hoạt động rất giống như subprocess.comunicateở chỗ nó chấm dứt quá trình sau một thao tác đọc / ghi. Tôi cũng thấy rằng bạn đang sử dụng một đường ống duy nhất stdout, quy trình con xử lý như không chặn. Cố gắng sử dụng cả hai stdoutstderr tôi thấy tôi cuối cùng đã chặn .
Carel

@Carel mã trong câu trả lời hoạt động như dự định như được mô tả trong câu trả lời một cách rõ ràng. Có thể thực hiện các hành vi khác nếu muốn. Cả hai ống đều không chặn bằng nhau nếu được sử dụng, đây là một ví dụ về cách đọc đồng thời từ cả hai ống .
jfs

19

Hãy thử mô-đun asyncproc . Ví dụ:

import os
from asyncproc import Process
myProc = Process("myprogram.app")

while True:
    # check to see if process has ended
    poll = myProc.wait(os.WNOHANG)
    if poll != None:
        break
    # print any new output
    out = myProc.read()
    if out != "":
        print out

Các mô-đun chăm sóc tất cả các luồng theo đề xuất của S.Lott.


1
Hoàn toàn rực rỡ. Dễ dàng hơn nhiều so với mô đun quy trình thô. Hoạt động hoàn hảo cho tôi trên Ubuntu.
Cerin

12
asyncproc không hoạt động trên windows và windows không hỗ trợ os.WNOHANG :-(
Bryan Oakley

26
asyncproc là GPL, điều này hạn chế hơn nữa việc sử dụng nó :-(
Bryan Oakley

Cảm ơn. Một điều nhỏ: Dường như thay thế các tab bằng 8 khoảng trắng trong asyncproc.py là cách để đi :)
benjaoming

Có vẻ như bạn không thể lấy mã trả về của quy trình mà bạn đã khởi chạy thông qua mô-đun asyncproc; chỉ đầu ra mà nó tạo ra.
Grayaii

17

Bạn có thể làm điều này thực sự dễ dàng trong Twisted . Tùy thuộc vào cơ sở mã hiện tại của bạn, điều này có thể không dễ sử dụng, nhưng nếu bạn đang xây dựng một ứng dụng xoắn, thì những thứ như thế này trở nên gần như không đáng kể. Bạn tạo một ProcessProtocollớp và ghi đè outReceived()phương thức. Twisted (tùy thuộc vào lò phản ứng được sử dụng) thường chỉ là một select()vòng lặp lớn với các cuộc gọi lại được cài đặt để xử lý dữ liệu từ các mô tả tệp khác nhau (thường là ổ cắm mạng). Vì vậy, outReceived()phương pháp chỉ đơn giản là cài đặt một cuộc gọi lại để xử lý dữ liệu đến từ STDOUT. Một ví dụ đơn giản thể hiện hành vi này như sau:

from twisted.internet import protocol, reactor

class MyProcessProtocol(protocol.ProcessProtocol):

    def outReceived(self, data):
        print data

proc = MyProcessProtocol()
reactor.spawnProcess(proc, './myprogram', ['./myprogram', 'arg1', 'arg2', 'arg3'])
reactor.run()

Các tài liệu Twisted có một số thông tin hữu ích về vấn đề này.

Nếu bạn xây dựng toàn bộ ứng dụng của mình xung quanh Twisted, nó sẽ tạo ra giao tiếp không đồng bộ với các quy trình khác, cục bộ hoặc từ xa, thực sự thanh lịch như thế này. Mặt khác, nếu chương trình của bạn không được xây dựng trên Twisted, điều này thực sự sẽ không hữu ích. Hy vọng rằng điều này có thể hữu ích cho những người đọc khác, ngay cả khi nó không áp dụng cho ứng dụng cụ thể của bạn.


không tốt. selectkhông nên làm việc trên các cửa sổ với các mô tả tập tin, theo tài liệu
n611x007

2
@naxa Tôi không nghĩ select()anh ấy đang nói đến giống như bạn. Tôi giả sử điều này bởi vì Twistedhoạt động trên windows ...
notbad.jpeg


1
"Twisted (tùy thuộc vào lò phản ứng được sử dụng) thường chỉ là một vòng lặp select () lớn" có nghĩa là có một số lò phản ứng để lựa chọn giữa. Các select()một là một trong những di động nhất trên unixes và unix-thích, nhưng cũng có hai lò phản ứng có sẵn cho Windows: twistedmatrix.com/documents/current/core/howto/...
clacke

14

Sử dụng chọn và đọc (1).

import subprocess     #no new requirements
def readAllSoFar(proc, retVal=''): 
  while (select.select([proc.stdout],[],[],0)[0]!=[]):   
    retVal+=proc.stdout.read(1)
  return retVal
p = subprocess.Popen(['/bin/ls'], stdout=subprocess.PIPE)
while not p.poll():
  print (readAllSoFar(p))

Đối với readline () - như:

lines = ['']
while not p.poll():
  lines = readAllSoFar(p, lines[-1]).split('\n')
  for a in range(len(lines)-1):
    print a
lines = readAllSoFar(p, lines[-1]).split('\n')
for a in range(len(lines)-1):
  print a

6
không tốt. selectkhông nên làm việc trên các cửa sổ với các mô tả tập tin, theo tài liệu
n611x007

CHÚA ƠI. Đọc megabyte, hoặc có thể mỗi gigabyte một ký tự ... đó là ý tưởng tồi tệ nhất tôi từng thấy trong một thời gian dài ... không cần phải đề cập, mã này không hoạt động, vì cho proc.stdout.read()dù đối số có nhỏ đến đâu một cuộc gọi chặn.
wvxvw

OSError: [WinError 10093] Either the application has not called WSAStartup, or WSAStartup failed
nmz787

8

Một giải pháp là tạo một quy trình khác để thực hiện quá trình đọc của bạn hoặc tạo một chuỗi của quy trình với thời gian chờ.

Đây là phiên bản luồng của chức năng hết thời gian:

http://code.activestate.com/recipes/473878/

Tuy nhiên, bạn có cần đọc thiết bị xuất chuẩn khi nó đến không? Một giải pháp khác có thể là kết xuất đầu ra vào một tệp và đợi quá trình kết thúc bằng p.wait () .

f = open('myprogram_output.txt','w')
p = subprocess.Popen('myprogram.exe', stdout=f)
p.wait()
f.close()


str = open('myprogram_output.txt','r').read()

có vẻ như chủ đề của recpie sẽ không thoát ra sau khi hết thời gian và giết nó tùy thuộc vào việc có thể giết tiến trình con (sg. nếu không liên quan đến vấn đề này) thì nó sẽ đọc (một điều bạn nên có thể nhưng chỉ trong trường hợp bạn không thể ..) .
n611x007

7

Disclaimer: điều này chỉ hoạt động cho cơn lốc xoáy

Bạn có thể làm điều này bằng cách đặt fd thành không chặn và sau đó sử dụng ioloop để đăng ký gọi lại. Tôi đã đóng gói cái này trong một quả trứng gọi là tornado_sub process và bạn có thể cài đặt nó thông qua PyPI:

easy_install tornado_subprocess

bây giờ bạn có thể làm một cái gì đó như thế này:

import tornado_subprocess
import tornado.ioloop

    def print_res( status, stdout, stderr ) :
    print status, stdout, stderr
    if status == 0:
        print "OK:"
        print stdout
    else:
        print "ERROR:"
        print stderr

t = tornado_subprocess.Subprocess( print_res, timeout=30, args=[ "cat", "/etc/passwd" ] )
t.start()
tornado.ioloop.IOLoop.instance().start()

bạn cũng có thể sử dụng nó với RequestHandler

class MyHandler(tornado.web.RequestHandler):
    def on_done(self, status, stdout, stderr):
        self.write( stdout )
        self.finish()

    @tornado.web.asynchronous
    def get(self):
        t = tornado_subprocess.Subprocess( self.on_done, timeout=30, args=[ "cat", "/etc/passwd" ] )
        t.start()

Cảm ơn các tính năng tốt đẹp! Chỉ cần làm rõ, tại sao chúng ta không thể đơn giản sử dụng threading.Threadđể tạo các quy trình không chặn mới? Tôi đã sử dụng nó trong on_messageví dụ của Tornado websocket và nó đã làm rất tốt.
VisioN

1
luồng chủ yếu được khuyến khích trong cơn lốc xoáy. chúng tốt cho các chức năng nhỏ, chạy ngắn. Bạn có thể đọc về nó ở đây: stackoverflow.com/questions/7846323/tornado-web-and-threads github.com/facebook/tornado/wiki/Threading-and-concurrency
Vukasin Toroman

@VukasinToroman bạn thực sự đã cứu tôi ở đây với điều này. cảm ơn bạn rất nhiều về mô-đun tornado_sub process :)
James Gentes

cái này có hoạt động trên windows không? (lưu ý rằng select, với phần mô tả tệp, thì không )
n611x007

Lib này không sử dụng selectcuộc gọi. Tôi đã không thử điều này trong Windows nhưng có lẽ bạn sẽ gặp rắc rối vì lib đang sử dụng fcntlmô-đun. Vì vậy, trong ngắn hạn: không có điều này có thể sẽ không hoạt động trong Windows.
Vukasin Toroman

6

Các giải pháp hiện tại không làm việc cho tôi (chi tiết bên dưới). Điều cuối cùng đã làm là triển khai readline bằng cách đọc (1) (dựa trên câu trả lời này ). Cái sau không chặn:

from subprocess import Popen, PIPE
from threading import Thread
def process_output(myprocess): #output-consuming thread
    nextline = None
    buf = ''
    while True:
        #--- extract line using read(1)
        out = myprocess.stdout.read(1)
        if out == '' and myprocess.poll() != None: break
        if out != '':
            buf += out
            if out == '\n':
                nextline = buf
                buf = ''
        if not nextline: continue
        line = nextline
        nextline = None

        #--- do whatever you want with line here
        print 'Line is:', line
    myprocess.stdout.close()

myprocess = Popen('myprogram.exe', stdout=PIPE) #output-producing process
p1 = Thread(target=process_output, args=(dcmpid,)) #output-consuming thread
p1.daemon = True
p1.start()

#--- do whatever here and then kill process and thread if needed
if myprocess.poll() == None: #kill process; will automatically stop thread
    myprocess.kill()
    myprocess.wait()
if p1 and p1.is_alive(): #wait for thread to finish
    p1.join()

Tại sao các giải pháp hiện tại không hoạt động:

  1. Các giải pháp yêu cầu đường đọc (bao gồm các giải pháp dựa trên Hàng đợi) luôn luôn chặn. Rất khó (không thể?) Để giết luồng xử lý đường dẫn. Nó chỉ bị giết khi quá trình tạo ra nó kết thúc, nhưng không bị giết khi quá trình tạo đầu ra bị giết.
  2. Trộn fcntl cấp thấp với các cuộc gọi đọc cấp cao có thể không hoạt động đúng như anonnn đã chỉ ra.
  3. Sử dụng select.poll () rất gọn gàng, nhưng không hoạt động trên Windows theo tài liệu python.
  4. Sử dụng các thư viện của bên thứ ba có vẻ quá mức cho nhiệm vụ này và thêm các phụ thuộc bổ sung.

1
1. q.get_nowait()từ câu trả lời của tôi không được chặn, bao giờ, đó là điểm sử dụng nó. 2. Chuỗi thực thi readline ( enqueue_output()hàm ) thoát trên EOF, ví dụ, bao gồm cả trường hợp khi quá trình sản xuất đầu ra bị giết. Nếu bạn tin rằng nó không phải như vậy; xin vui lòng, cung cấp một ví dụ mã tối thiểu hoàn chỉnh hiển thị khác (có thể là một câu hỏi mới ).
jfs

1
@sebastian Tôi đã dành một giờ hoặc nhiều hơn để cố gắng đưa ra một ví dụ tối thiểu. Cuối cùng tôi phải đồng ý rằng câu trả lời của bạn xử lý tất cả các trường hợp. Tôi đoán nó không hoạt động sớm hơn đối với tôi bởi vì khi tôi đang cố gắng giết quá trình sản xuất đầu ra, nó đã bị giết và đưa ra một lỗi khó gỡ lỗi. Giờ đã được sử dụng tốt, bởi vì trong khi đưa ra một ví dụ tối thiểu, tôi có thể đưa ra một giải pháp đơn giản hơn.
Vikram Pudi

Bạn có thể gửi giải pháp đơn giản hơn không? :) (nếu nó khác với Sebastian)
n611x007

@ nguy hiểm89: Tôi nghĩ dcmpid = myprocess.
ViFI

Trong điều kiện sau khi đọc () gọi (ngay sau khi True): out sẽ không bao giờ là chuỗi rỗng vì bạn đọc ít nhất chuỗi / byte có độ dài là 1.
sergzach

6

Đây là mã của tôi, được sử dụng để bắt mọi đầu ra từ quy trình con càng sớm càng tốt, bao gồm cả các dòng một phần. Nó bơm cùng lúc và stdout và stderr theo thứ tự gần như đúng.

Đã kiểm tra và làm việc chính xác trên Python 2.7 linux & windows.

#!/usr/bin/python
#
# Runner with stdout/stderr catcher
#
from sys import argv
from subprocess import Popen, PIPE
import os, io
from threading import Thread
import Queue
def __main__():
    if (len(argv) > 1) and (argv[-1] == "-sub-"):
        import time, sys
        print "Application runned!"
        time.sleep(2)
        print "Slept 2 second"
        time.sleep(1)
        print "Slept 1 additional second",
        time.sleep(2)
        sys.stderr.write("Stderr output after 5 seconds")
        print "Eol on stdin"
        sys.stderr.write("Eol on stderr\n")
        time.sleep(1)
        print "Wow, we have end of work!",
    else:
        os.environ["PYTHONUNBUFFERED"]="1"
        try:
            p = Popen( argv + ["-sub-"],
                       bufsize=0, # line-buffered
                       stdin=PIPE, stdout=PIPE, stderr=PIPE )
        except WindowsError, W:
            if W.winerror==193:
                p = Popen( argv + ["-sub-"],
                           shell=True, # Try to run via shell
                           bufsize=0, # line-buffered
                           stdin=PIPE, stdout=PIPE, stderr=PIPE )
            else:
                raise
        inp = Queue.Queue()
        sout = io.open(p.stdout.fileno(), 'rb', closefd=False)
        serr = io.open(p.stderr.fileno(), 'rb', closefd=False)
        def Pump(stream, category):
            queue = Queue.Queue()
            def rdr():
                while True:
                    buf = stream.read1(8192)
                    if len(buf)>0:
                        queue.put( buf )
                    else:
                        queue.put( None )
                        return
            def clct():
                active = True
                while active:
                    r = queue.get()
                    try:
                        while True:
                            r1 = queue.get(timeout=0.005)
                            if r1 is None:
                                active = False
                                break
                            else:
                                r += r1
                    except Queue.Empty:
                        pass
                    inp.put( (category, r) )
            for tgt in [rdr, clct]:
                th = Thread(target=tgt)
                th.setDaemon(True)
                th.start()
        Pump(sout, 'stdout')
        Pump(serr, 'stderr')

        while p.poll() is None:
            # App still working
            try:
                chan,line = inp.get(timeout = 1.0)
                if chan=='stdout':
                    print "STDOUT>>", line, "<?<"
                elif chan=='stderr':
                    print " ERROR==", line, "=?="
            except Queue.Empty:
                pass
        print "Finish"

if __name__ == '__main__':
    __main__()

Một trong số ít câu trả lời cho phép bạn đọc những thứ không nhất thiết phải kết thúc bằng một dòng mới.
totaam

5

Tôi thêm vấn đề này để đọc một số quy trình con.Popen stdout. Đây là giải pháp đọc không chặn của tôi:

import fcntl

def non_block_read(output):
    fd = output.fileno()
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    try:
        return output.read()
    except:
        return ""

# Use example
from subprocess import *
sb = Popen("echo test && sleep 1000", shell=True, stdout=PIPE)
sb.kill()

# sb.stdout.read() # <-- This will block
non_block_read(sb.stdout)
'test\n'

5
fcntl không hoạt động trên windows, theo các tài liệu .
n611x007

@anatolytechtonik sử dụng msvcrt.kbhit()thay thế
mèo

4

Phiên bản đọc không chặn này không yêu cầu các mô-đun đặc biệt và sẽ hoạt động vượt trội trên hầu hết các bản phân phối Linux.

import os
import sys
import time
import fcntl
import subprocess

def async_read(fd):
    # set non-blocking flag while preserving old flags
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
    # read char until EOF hit
    while True:
        try:
            ch = os.read(fd.fileno(), 1)
            # EOF
            if not ch: break                                                                                                                                                              
            sys.stdout.write(ch)
        except OSError:
            # waiting for data be available on fd
            pass

def shell(args, async=True):
    # merge stderr and stdout
    proc = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if async: async_read(proc.stdout)
    sout, serr = proc.communicate()
    return (sout, serr)

if __name__ == '__main__':
    cmd = 'ping 8.8.8.8'
    sout, serr = shell(cmd.split())

3

Đây là một giải pháp đơn giản dựa trên các chủ đề:

  • hoạt động trên cả Linux và Windows (không phụ thuộc vào select).
  • đọc cả stdoutstderrkhông đồng bộ.
  • không phụ thuộc vào việc bỏ phiếu chủ động với thời gian chờ tùy ý (thân thiện với CPU).
  • không sử dụng asyncio(có thể xung đột với các thư viện khác).
  • chạy cho đến khi quá trình con kết thúc.

máy in

import time
import sys

sys.stdout.write("Hello\n")
sys.stdout.flush()
time.sleep(1)
sys.stdout.write("World!\n")
sys.stdout.flush()
time.sleep(1)
sys.stderr.write("That's an error\n")
sys.stderr.flush()
time.sleep(2)
sys.stdout.write("Actually, I'm fine\n")
sys.stdout.flush()
time.sleep(1)

độc giả

import queue
import subprocess
import sys
import threading


def enqueue_stream(stream, queue, type):
    for line in iter(stream.readline, b''):
        queue.put(str(type) + line.decode('utf-8'))
    stream.close()


def enqueue_process(process, queue):
    process.wait()
    queue.put('x')


p = subprocess.Popen('python printer.py', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
q = queue.Queue()
to = threading.Thread(target=enqueue_stream, args=(p.stdout, q, 1))
te = threading.Thread(target=enqueue_stream, args=(p.stderr, q, 2))
tp = threading.Thread(target=enqueue_process, args=(p, q))
te.start()
to.start()
tp.start()

while True:
    line = q.get()
    if line[0] == 'x':
        break
    if line[0] == '2':  # stderr
        sys.stdout.write("\033[0;31m")  # ANSI red color
    sys.stdout.write(line[1:])
    if line[0] == '2':
        sys.stdout.write("\033[0m")  # reset ANSI code
    sys.stdout.flush()

tp.join()
to.join()
te.join()

2

Thêm câu trả lời này vào đây vì nó cung cấp khả năng thiết lập các đường ống không chặn trên Windows và Unix.

Tất cả các ctypeschi tiết là nhờ câu trả lời của @ techtonik .

Có một phiên bản sửa đổi một chút sẽ được sử dụng trên cả hệ thống Unix và Windows.

  • Tương thích Python3 (chỉ cần thay đổi nhỏ) .
  • Bao gồm phiên bản posix và xác định ngoại lệ để sử dụng cho một trong hai.

Bằng cách này, bạn có thể sử dụng cùng chức năng và ngoại lệ cho mã Unix và Windows.

# pipe_non_blocking.py (module)
"""
Example use:

    p = subprocess.Popen(
            command,
            stdout=subprocess.PIPE,
            )

    pipe_non_blocking_set(p.stdout.fileno())

    try:
        data = os.read(p.stdout.fileno(), 1)
    except PortableBlockingIOError as ex:
        if not pipe_non_blocking_is_error_blocking(ex):
            raise ex
"""


__all__ = (
    "pipe_non_blocking_set",
    "pipe_non_blocking_is_error_blocking",
    "PortableBlockingIOError",
    )

import os


if os.name == "nt":
    def pipe_non_blocking_set(fd):
        # Constant could define globally but avoid polluting the name-space
        # thanks to: /programming/34504970
        import msvcrt

        from ctypes import windll, byref, wintypes, WinError, POINTER
        from ctypes.wintypes import HANDLE, DWORD, BOOL

        LPDWORD = POINTER(DWORD)

        PIPE_NOWAIT = wintypes.DWORD(0x00000001)

        def pipe_no_wait(pipefd):
            SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
            SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
            SetNamedPipeHandleState.restype = BOOL

            h = msvcrt.get_osfhandle(pipefd)

            res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
            if res == 0:
                print(WinError())
                return False
            return True

        return pipe_no_wait(fd)

    def pipe_non_blocking_is_error_blocking(ex):
        if not isinstance(ex, PortableBlockingIOError):
            return False
        from ctypes import GetLastError
        ERROR_NO_DATA = 232

        return (GetLastError() == ERROR_NO_DATA)

    PortableBlockingIOError = OSError
else:
    def pipe_non_blocking_set(fd):
        import fcntl
        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        return True

    def pipe_non_blocking_is_error_blocking(ex):
        if not isinstance(ex, PortableBlockingIOError):
            return False
        return True

    PortableBlockingIOError = BlockingIOError

Để tránh đọc dữ liệu không đầy đủ, cuối cùng tôi đã viết trình tạo dòng đọc của riêng mình (trả về chuỗi byte cho mỗi dòng).

Đây là một máy phát điện để bạn có thể lấy ví dụ ...

def non_blocking_readlines(f, chunk=1024):
    """
    Iterate over lines, yielding b'' when nothings left
    or when new data is not yet available.

    stdout_iter = iter(non_blocking_readlines(process.stdout))

    line = next(stdout_iter)  # will be a line or b''.
    """
    import os

    from .pipe_non_blocking import (
            pipe_non_blocking_set,
            pipe_non_blocking_is_error_blocking,
            PortableBlockingIOError,
            )

    fd = f.fileno()
    pipe_non_blocking_set(fd)

    blocks = []

    while True:
        try:
            data = os.read(fd, chunk)
            if not data:
                # case were reading finishes with no trailing newline
                yield b''.join(blocks)
                blocks.clear()
        except PortableBlockingIOError as ex:
            if not pipe_non_blocking_is_error_blocking(ex):
                raise ex

            yield b''
            continue

        while True:
            n = data.find(b'\n')
            if n == -1:
                break

            yield b''.join(blocks) + data[:n + 1]
            data = data[n + 1:]
            blocks.clear()
        blocks.append(data)

(1) nhận xét này cho biết rằng readline()không hoạt động với các đường ống không chặn (chẳng hạn như được sử dụng fcntl) trên Python 2 - bạn có nghĩ rằng nó không còn đúng nữa không? (câu trả lời của tôi chứa liên kết ( fcntl) cung cấp thông tin tương tự nhưng dường như nó đã bị xóa ngay bây giờ). (2) Xem cách multiprocessing.connection.Pipesử dụngSetNamedPipeHandleState
jfs

Tôi chỉ thử nghiệm điều này trên Python3. Nhưng nhìn thấy thông tin này quá và mong đợi nó vẫn còn hiệu lực. Tôi cũng đã viết mã của riêng mình để sử dụng thay cho đường dẫn, tôi đã cập nhật câu trả lời của mình để đưa nó vào.
ideaman42

2

Tôi có vấn đề của người hỏi ban đầu, nhưng không muốn gọi các chủ đề. Tôi đã trộn giải pháp của Jesse với đọc trực tiếp () từ đường ống và trình xử lý bộ đệm của riêng tôi để đọc dòng (tuy nhiên, quy trình phụ của tôi - ping - luôn viết đầy đủ các dòng <kích thước trang hệ thống). Tôi tránh bận rộn chờ đợi bằng cách chỉ đọc trong một chiếc đồng hồ io đã đăng ký gobject. Những ngày này, tôi thường chạy mã trong một yêu tinh MainLoop để tránh các luồng.

def set_up_ping(ip, w):
# run the sub-process
# watch the resultant pipe
p = subprocess.Popen(['/bin/ping', ip], stdout=subprocess.PIPE)
# make stdout a non-blocking file
fl = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
stdout_gid = gobject.io_add_watch(p.stdout, gobject.IO_IN, w)
return stdout_gid # for shutting down

Người theo dõi là

def watch(f, *other):
print 'reading',f.read()
return True

Và chương trình chính thiết lập một ping và sau đó gọi vòng lặp gobject mail.

def main():
set_up_ping('192.168.1.8', watch)
# discard gid as unused here
gobject.MainLoop().run()

Bất kỳ công việc nào khác được gắn vào các cuộc gọi lại trong gobject.


2

Mọi thứ tốt hơn rất nhiều trong Python hiện đại.

Đây là một chương trình con đơn giản, "hello.py":

#!/usr/bin/env python3

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

Và một chương trình để tương tác với nó:

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())

Điều đó in ra:

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

Lưu ý rằng mẫu thực tế, cũng gần như tất cả các câu trả lời trước đó, cả ở đây và trong các câu hỏi liên quan, là đặt bộ mô tả tệp stdout của trẻ thành không chặn và sau đó bỏ phiếu trong một số vòng lặp chọn. Những ngày này, tất nhiên, vòng lặp đó được cung cấp bởi asyncio.


1

Các lựa chọn mô-đun giúp bạn xác định nơi các đầu vào hữu ích tiếp theo là.

Tuy nhiên, bạn hầu như luôn luôn hạnh phúc hơn với các chủ đề riêng biệt. Một người chặn đọc stdin, người khác thực hiện bất cứ nơi nào bạn không muốn bị chặn.


11
Tôi nghĩ rằng câu trả lời này không hữu ích vì hai lý do: (a) Mô-đun được chọn sẽ không hoạt động trên các đường ống trong Windows (vì liên kết được cung cấp rõ ràng), điều này đánh bại ý định của OP để có giải pháp di động. (b) Các luồng không đồng bộ không cho phép đối thoại đồng bộ giữa cha mẹ và tiến trình con. Điều gì xảy ra nếu quá trình cha mẹ muốn gửi hành động tiếp theo theo dòng tiếp theo được đọc từ đứa trẻ?!
ThomasH

4
select cũng không hữu ích vì các lần đọc của Python sẽ chặn ngay cả sau khi chọn, vì nó không có ngữ nghĩa chuẩn C và sẽ không trả về dữ liệu một phần.
Helmut Grohne

Một cách riêng biệt để đọc từ đầu ra của trẻ em đã giải quyết vấn đề của tôi tương tự như thế này. Nếu bạn cần tương tác đồng bộ, tôi đoán bạn không thể sử dụng giải pháp này (trừ khi bạn biết đầu ra nào sẽ xảy ra). Tôi đã chấp nhận câu trả lời này
Emiliano

1

Tại sao làm phiền chủ đề & hàng đợi? Không giống như readline (), BufferedReader.read1 () sẽ không chờ \ r \ n, nó trả về ASAP nếu có bất kỳ đầu ra nào xuất hiện.

#!/usr/bin/python
from subprocess import Popen, PIPE, STDOUT
import io

def __main__():
    try:
        p = Popen( ["ping", "-n", "3", "127.0.0.1"], stdin=PIPE, stdout=PIPE, stderr=STDOUT )
    except: print("Popen failed"); quit()
    sout = io.open(p.stdout.fileno(), 'rb', closefd=False)
    while True:
        buf = sout.read1(1024)
        if len(buf) == 0: break
        print buf,

if __name__ == '__main__':
    __main__()

Nó sẽ trả lại càng sớm càng tốt nếu không có gì đến? Nếu nó không bị chặn.
Mathieu Pagé

@ MathieuPagé nói đúng. read1sẽ chặn nếu các khối đọc cơ bản đầu tiên, xảy ra khi đường ống vẫn mở nhưng không có đầu vào nào khả dụng.
Jack O'Connor

1

Trong trường hợp của tôi, tôi cần một mô-đun ghi nhật ký để bắt đầu ra từ các ứng dụng nền và tăng nó (thêm dấu thời gian, màu sắc, v.v.).

Tôi đã kết thúc với một chủ đề nền làm I / O thực tế. Mã sau chỉ dành cho nền tảng POSIX. Tôi lột những phần không cần thiết.

Nếu ai đó sẽ sử dụng con thú này trong thời gian dài, hãy xem xét việc quản lý các mô tả mở. Trong trường hợp của tôi, nó không phải là một vấn đề lớn.

# -*- python -*-
import fcntl
import threading
import sys, os, errno
import subprocess

class Logger(threading.Thread):
    def __init__(self, *modules):
        threading.Thread.__init__(self)
        try:
            from select import epoll, EPOLLIN
            self.__poll = epoll()
            self.__evt = EPOLLIN
            self.__to = -1
        except:
            from select import poll, POLLIN
            print 'epoll is not available'
            self.__poll = poll()
            self.__evt = POLLIN
            self.__to = 100
        self.__fds = {}
        self.daemon = True
        self.start()

    def run(self):
        while True:
            events = self.__poll.poll(self.__to)
            for fd, ev in events:
                if (ev&self.__evt) != self.__evt:
                    continue
                try:
                    self.__fds[fd].run()
                except Exception, e:
                    print e

    def add(self, fd, log):
        assert not self.__fds.has_key(fd)
        self.__fds[fd] = log
        self.__poll.register(fd, self.__evt)

class log:
    logger = Logger()

    def __init__(self, name):
        self.__name = name
        self.__piped = False

    def fileno(self):
        if self.__piped:
            return self.write
        self.read, self.write = os.pipe()
        fl = fcntl.fcntl(self.read, fcntl.F_GETFL)
        fcntl.fcntl(self.read, fcntl.F_SETFL, fl | os.O_NONBLOCK)
        self.fdRead = os.fdopen(self.read)
        self.logger.add(self.read, self)
        self.__piped = True
        return self.write

    def __run(self, line):
        self.chat(line, nl=False)

    def run(self):
        while True:
            try: line = self.fdRead.readline()
            except IOError, exc:
                if exc.errno == errno.EAGAIN:
                    return
                raise
            self.__run(line)

    def chat(self, line, nl=True):
        if nl: nl = '\n'
        else: nl = ''
        sys.stdout.write('[%s] %s%s' % (self.__name, line, nl))

def system(command, param=[], cwd=None, env=None, input=None, output=None):
    args = [command] + param
    p = subprocess.Popen(args, cwd=cwd, stdout=output, stderr=output, stdin=input, env=env, bufsize=0)
    p.wait()

ls = log('ls')
ls.chat('go')
system("ls", ['-l', '/'], output=ls)

date = log('date')
date.chat('go')
system("date", output=date)

1

Vấn đề của tôi hơi khác một chút khi tôi muốn thu thập cả thiết bị xuất chuẩn và thiết bị xuất chuẩn từ một quy trình đang chạy, nhưng cuối cùng vẫn như vậy vì tôi muốn kết xuất đầu ra trong một tiện ích như được tạo.

Tôi không muốn sử dụng nhiều cách giải quyết được đề xuất bằng cách sử dụng Hàng đợi hoặc Chủ đề bổ sung vì chúng không cần thiết để thực hiện một tác vụ phổ biến như chạy tập lệnh khác và thu thập đầu ra của nó.

Sau khi đọc các giải pháp đề xuất và tài liệu python, tôi đã giải quyết vấn đề của mình bằng cách thực hiện bên dưới. Có, nó chỉ hoạt động cho POSIX khi tôi đang sử dụng selectchức năng gọi.

Tôi đồng ý rằng các tài liệu là khó hiểu và việc thực hiện là khó xử cho một nhiệm vụ kịch bản phổ biến như vậy. Tôi tin rằng các phiên bản cũ hơn của python có mặc định Popenkhác nhau và giải thích khác nhau do đó tạo ra rất nhiều nhầm lẫn. Điều này dường như hoạt động tốt cho cả Python 2.7.12 và 3.5.2.

Chìa khóa là để thiết lập bufsize=1bộ đệm dòng và sau đó universal_newlines=Truexử lý dưới dạng tệp văn bản thay vì tệp nhị phân dường như trở thành mặc định khi cài đặt bufsize=1.

class workerThread(QThread):
   def __init__(self, cmd):
      QThread.__init__(self)
      self.cmd = cmd
      self.result = None           ## return code
      self.error = None            ## flag indicates an error
      self.errorstr = ""           ## info message about the error

   def __del__(self):
      self.wait()
      DEBUG("Thread removed")

   def run(self):
      cmd_list = self.cmd.split(" ")   
      try:
         cmd = subprocess.Popen(cmd_list, bufsize=1, stdin=None
                                        , universal_newlines=True
                                        , stderr=subprocess.PIPE
                                        , stdout=subprocess.PIPE)
      except OSError:
         self.error = 1
         self.errorstr = "Failed to execute " + self.cmd
         ERROR(self.errorstr)
      finally:
         VERBOSE("task started...")
      import select
      while True:
         try:
            r,w,x = select.select([cmd.stdout, cmd.stderr],[],[])
            if cmd.stderr in r:
               line = cmd.stderr.readline()
               if line != "":
                  line = line.strip()
                  self.emit(SIGNAL("update_error(QString)"), line)
            if cmd.stdout in r:
               line = cmd.stdout.readline()
               if line == "":
                  break
               line = line.strip()
               self.emit(SIGNAL("update_output(QString)"), line)
         except IOError:
            pass
      cmd.wait()
      self.result = cmd.returncode
      if self.result < 0:
         self.error = 1
         self.errorstr = "Task terminated by signal " + str(self.result)
         ERROR(self.errorstr)
         return
      if self.result:
         self.error = 1
         self.errorstr = "exit code " + str(self.result)
         ERROR(self.errorstr)
         return
      return

ERROR, DEBUG và ĐỘNG TỪ chỉ đơn giản là các macro in đầu ra cho thiết bị đầu cuối.

Giải pháp này có hiệu quả IMHO 99,99% vì nó vẫn sử dụng readlinechức năng chặn , vì vậy chúng tôi giả sử quy trình phụ là tốt và xuất ra các dòng hoàn chỉnh.

Tôi hoan nghênh phản hồi để cải thiện giải pháp vì tôi vẫn chưa quen với Python.


Trong trường hợp cụ thể này, bạn có thể đặt stderr = sub process.STDOUT trong hàm tạo Popen và nhận tất cả đầu ra từ cmd.stdout.readline ().
Aaron

Đẹp ví dụ rõ ràng. Đã gặp sự cố với select.select () nhưng điều này đã giải quyết nó cho tôi.
maharvey67


0

Làm việc từ câu trả lời của JF Sebastian và một số nguồn khác, tôi đã kết hợp một trình quản lý quy trình con đơn giản. Nó cung cấp yêu cầu đọc không chặn, cũng như chạy song song một số quy trình. Nó không sử dụng bất kỳ cuộc gọi dành riêng cho hệ điều hành nào (mà tôi biết) và do đó sẽ hoạt động ở mọi nơi.

Nó có sẵn từ pypi, vì vậy chỉ cần pip install shelljob. Tham khảo trang dự án để biết ví dụ và tài liệu đầy đủ.


0

EDIT: Việc thực hiện này vẫn còn khối. Sử dụng câu trả lời của JFSebastian thay thế.

Tôi đã thử câu trả lời hàng đầu , nhưng rủi ro bổ sung và bảo trì mã luồng là đáng lo ngại.

Nhìn qua mô-đun io (và bị giới hạn ở 2.6), tôi thấy BufferedReader. Đây là giải pháp không chặn, không chặn của tôi.

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

bạn đã thử for line in iter(p.stdout.readline, ""): # do stuff with the linechưa Nó là không có luồng (luồng đơn) và chặn khi mã của bạn chặn.
jfs

@ jf-sebastian Vâng, cuối cùng tôi đã trở lại câu trả lời của bạn. Việc thực hiện của tôi vẫn thỉnh thoảng bị chặn. Tôi sẽ chỉnh sửa câu trả lời của mình để cảnh báo những người khác không đi xuống tuyến đường này.
romc

0

Gần đây tôi đã vấp phải cùng một vấn đề Tôi cần đọc một dòng tại thời điểm từ luồng (đuôi chạy trong quy trình con) ở chế độ không chặn Tôi muốn tránh các vấn đề tiếp theo: không ghi cpu, không đọc luồng theo một byte ( như readline đã làm), v.v.

Đây là cách triển khai của tôi https://gist.github.com/grubberr/5501e1a9760c3ables5e0a nó không hỗ trợ windows (thăm dò ý kiến), không xử lý EOF, nhưng nó hoạt động tốt với tôi


câu trả lời dựa trên thread nào không ghi cpu (bạn có thể chỉ định tùy tiện timeoutnhư trong giải pháp của bạn) và .readline()đọc nhiều hơn một byte tại một thời điểm ( bufsize=1có nghĩa là dòng -buffered (chỉ liên quan đến văn bản)). Những vấn đề khác bạn đã tìm thấy? Câu trả lời chỉ liên kết không phải là rất hữu ích.
JFS

0

Đây là một ví dụ để chạy lệnh tương tác trong quy trình con và thiết bị xuất chuẩn được tương tác bằng cách sử dụng thiết bị đầu cuối giả. Bạn có thể tham khảo: 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

Giải pháp này sử dụng select mô-đun để "đọc bất kỳ dữ liệu có sẵn" từ luồng IO. Hàm này chặn ban đầu cho đến khi có dữ liệu, nhưng sau đó chỉ đọc dữ liệu có sẵn và không chặn thêm nữa.

Với thực tế là nó sử dụng selectmô-đun, điều này chỉ hoạt động trên Unix.

Mã này hoàn toàn tuân thủ PEP8.

import select


def read_available(input_stream, max_bytes=None):
    """
    Blocks until any data is available, then all available data is then read and returned.
    This function returns an empty string when end of stream is reached.

    Args:
        input_stream: The stream to read from.
        max_bytes (int|None): The maximum number of bytes to read. This function may return fewer bytes than this.

    Returns:
        str
    """
    # Prepare local variables
    input_streams = [input_stream]
    empty_list = []
    read_buffer = ""

    # Initially block for input using 'select'
    if len(select.select(input_streams, empty_list, empty_list)[0]) > 0:

        # Poll read-readiness using 'select'
        def select_func():
            return len(select.select(input_streams, empty_list, empty_list, 0)[0]) > 0

        # Create while function based on parameters
        if max_bytes is not None:
            def while_func():
                return (len(read_buffer) < max_bytes) and select_func()
        else:
            while_func = select_func

        while True:
            # Read single byte at a time
            read_data = input_stream.read(1)
            if len(read_data) == 0:
                # End of stream
                break
            # Append byte to string buffer
            read_buffer += read_data
            # Check if more data is available
            if not while_func():
                break

    # Return read buffer
    return read_buffer

0

Tôi cũng phải đối mặt với vấn đề được Jesse mô tả và giải quyết nó bằng cách sử dụng "select" như Bradley , Andy và những người khác đã làm nhưng trong chế độ chặn để tránh vòng lặp bận rộn. Nó sử dụng một ống giả như một stdin giả. Các khối chọn và chờ cho stdin hoặc ống sẵn sàng. Khi nhấn phím, stdin sẽ bỏ khóa chọn và giá trị khóa có thể được truy xuất bằng lệnh đọc (1). Khi một luồng khác ghi vào đường ống thì đường ống sẽ bỏ chọn và nó có thể được coi là một dấu hiệu cho thấy nhu cầu về stdin đã hết. Đây là một số mã tham khảo:

import sys
import os
from select import select

# -------------------------------------------------------------------------    
# Set the pipe (fake stdin) to simulate a final key stroke
# which will unblock the select statement
readEnd, writeEnd = os.pipe()
readFile = os.fdopen(readEnd)
writeFile = os.fdopen(writeEnd, "w")

# -------------------------------------------------------------------------
def getKey():

    # Wait for stdin or pipe (fake stdin) to be ready
    dr,dw,de = select([sys.__stdin__, readFile], [], [])

    # If stdin is the one ready then read it and return value
    if sys.__stdin__ in dr:
        return sys.__stdin__.read(1)   # For Windows use ----> getch() from module msvcrt

    # Must finish
    else:
        return None

# -------------------------------------------------------------------------
def breakStdinRead():
    writeFile.write(' ')
    writeFile.flush()

# -------------------------------------------------------------------------
# MAIN CODE

# Get key stroke
key = getKey()

# Keyboard input
if key:
    # ... do your stuff with the key value

# Faked keystroke
else:
    # ... use of stdin finished

# -------------------------------------------------------------------------
# OTHER THREAD CODE

breakStdinRead()

GHI CHÚ: Để làm cho công việc này trong Windows, đường ống phải được thay thế bằng ổ cắm. Tôi đã không thử nó nhưng nó sẽ hoạt động theo tài liệu.
gonzaedu61

0

Hãy thử wExect , đó là sự thay thế của windows .

import wexpect

p = wexpect.spawn('myprogram.exe')
p.stdout.readline('.')               // regex pattern of any character
output_str = p.after()

0

Trên các hệ thống giống như Unix và Python 3.5+ os.set_blocking, có chính xác những gì nó nói.

import os
import time
import subprocess

cmd = 'python3', '-c', 'import time; [(print(i), time.sleep(1)) for i in range(5)]'
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
os.set_blocking(p.stdout.fileno(), False)
start = time.time()
while True:
    # first iteration always produces empty byte string in non-blocking mode
    for i in range(2):    
        line = p.stdout.readline()
        print(i, line)
        time.sleep(0.5)
    if time.time() > start + 5:
        break
p.terminate()

Kết quả này:

1 b''
2 b'0\n'
1 b''
2 b'1\n'
1 b''
2 b'2\n'
1 b''
2 b'3\n'
1 b''
2 b'4\n'

Với os.set_blockingnhận xét đó là:

0 b'0\n'
1 b'1\n'
0 b'2\n'
1 b'3\n'
0 b'4\n'
1 b''

-2

Đây là một mô-đun hỗ trợ đọc không chặn và ghi nền bằng python:

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

Cung cấp một chức năng,

nonblock_read sẽ đọc dữ liệu từ luồng, nếu có, nếu không thì trả về một chuỗi trống (hoặc Không có nếu luồng được đóng ở phía bên kia và tất cả dữ liệu có thể đã được đọc)

Bạn cũng có thể xem xét mô-đun python-sub process2,

https://pypi.python.org/pypi/python-sub process2

mà thêm vào mô-đun quy trình con. Vì vậy, trên đối tượng được trả về từ "sub process.Popen" được thêm một phương thức bổ sung, runInBackground. Điều này bắt đầu một luồng và trả về một đối tượng sẽ tự động được điền vào khi nội dung được ghi vào thiết bị xuất chuẩn / stderr, mà không chặn luồng chính của bạn.

Thưởng thức!


Tôi muốn dùng thử mô-đun không chặn này , nhưng tôi tương đối mới ở một số quy trình Linux. Chính xác làm thế nào để tôi cài đặt các thói quen này? Tôi đang chạy Raspbian Jessie, một hương vị của Debian Linux cho Raspberry Pi. Tôi đã thử 'sudo apt-get install nonblock' và python-nonblock và cả hai đều gặp lỗi - không tìm thấy. Tôi đã tải xuống tệp zip từ trang web này pypi.python.org/pypi/python-nonblock , nhưng không biết phải làm gì với nó. Cảm ơn .... RDK
RDK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.