Non-blocking read on a subprocess.PIPE in python
我正在使用子进程模块启动子进程并连接到它的输出流(stdout)。 我希望能够在其标准输出上执行非阻塞读取。 有没有办法让.readline非阻塞或在我调用
这是我现在的工作方式(如果没有数据可用,它会在
1 2 | p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE) output_str = p.stdout.readline() |
在这种情况下,
无论操作系统如何,无阻塞地读取流的可靠方法是使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 |
我经常遇到类似的问题;我经常编写的Python程序需要能够执行一些主要功能,同时从命令行(stdin)接受用户输入。简单地将用户输入处理功能放在另一个线程中并不能解决问题,因为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) |
在我看来,这比使用选择或信号模块解决这个问题要清晰一点,但是它再次只适用于UNIX ...
Python 3.4为异步IO -
该方法类似于@Bryan Ward基于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #!/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() |
请参阅文档中的"子流程"。
有一个高级接口
(使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/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' ') 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"))) |
- 启动子进程,将其stdout重定向到管道
- 从子进程'stdout异步读取一行
- 杀死子进程
- 等它退出
如有必要,每个步骤都可以通过超时秒限制。
尝试使用asyncproc模块。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
该模块负责S.Lott建议的所有线程。
您可以在Twisted中轻松完成此操作。根据您现有的代码库,这可能不是那么容易使用,但如果您正在构建一个扭曲的应用程序,那么这样的事情几乎变得微不足道。您创建一个
1 2 3 4 5 6 7 8 9 10 | 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() |
Twisted文档有一些很好的信息。
如果你围绕Twisted构建整个应用程序,它会与本地或远程的其他进程进行异步通信,就像这样非常优雅。另一方面,如果你的程序不是建立在Twisted之上,那么这实际上并没有那么有用。希望这对其他读者有帮助,即使它不适用于您的特定应用程序。
使用选择&读(1)。
1 2 3 4 5 6 7 8 | 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)) |
对于readline() - 如:
1 2 3 4 5 6 7 8 9 10 | lines = [''] while not p.poll(): lines = readAllSoFar(p, lines[-1]).split(' ') for a in range(len(lines)-1): print a lines = readAllSoFar(p, lines[-1]).split(' ') for a in range(len(lines)-1): print a |
一种解决方案是使另一个进程执行您对进程的读取,或者使进程的线程超时。
这是超时函数的线程版本:
http://code.activestate.com/recipes/473878/
但是,你需要阅读stdout,因为它正在进入?
另一种解决方案可能是将输出转储到文件并等待进程使用p.wait()完成。
1 2 3 4 5 6 7 | f = open('myprogram_output.txt','w') p = subprocess.Popen('myprogram.exe', stdout=f) p.wait() f.close() str = open('myprogram_output.txt','r').read() |
免责声明:这仅适用于龙卷风
您可以通过将fd设置为非阻塞来执行此操作,然后使用ioloop注册回调。我把它打包成一个名为tornado_subprocess的蛋,你可以通过PyPI安装它:
1 | easy_install tornado_subprocess |
现在你可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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() |
您也可以将它与RequestHandler一起使用
1 2 3 4 5 6 7 8 9 | 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() |
现有的解决方案对我不起作用(详情如下)。最终工作的是使用read(1)实现readline(基于这个答案)。后者不会阻止:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 == ' ': 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() |
为什么现有解决方案不起作用:
此版本的非阻塞读取不需要特殊模块,并且可以在大多数Linux发行版中实现开箱即用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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()) |
我添加此问题来读取一些subprocess.Popen标准输出。
这是我的非阻塞读取解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 ' |
这是我的代码,用于捕获子进程ASAP的每个输出,包括部分行。它以相同的顺序同时泵送stdout和stderr。
经过测试并正确使用Python 2.7 linux&amp;视窗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | #!/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 ") 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__() |
在这里添加这个答案,因为它提供了在Windows和Unix上设置非阻塞管道的能力。
所有
在Unix和Windows系统上都有一个稍微修改过的版本。
- Python3兼容(只需要很小的改动)。
- 包括posix版本,并定义要用于其中的例外。
这样,您可以对Unix和Windows代码使用相同的函数和异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | # 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: https://stackoverflow.com/questions/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 |
为了避免读取不完整的数据,我最终编写了自己的readline生成器(返回每行的字节串)。
它是一个发电机,所以你可以...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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' ') if n == -1: break yield b''.join(blocks) + data[:n + 1] data = data[n + 1:] blocks.clear() blocks.append(data) |
我有原始提问者的问题,但不想调用线程。我将Jesse的解决方案与来自管道的直接read()和我自己的用于行读取的缓冲处理程序混合在一起(但是,我的子进程 - ping - 总是写完整行<系统页面大小)。我只是通过阅读gobject-registered io手表来避免忙碌等待。这些天我通常在gobject MainLoop中运行代码以避免线程。
1 2 3 4 5 6 7 8 9 | 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 |
观察者是
1 2 3 | def watch(f, *other): print 'reading',f.read() return True |
主程序设置ping然后调用gobject邮件循环。
1 2 3 4 | def main(): set_up_ping('192.168.1.8', watch) # discard gid as unused here gobject.MainLoop().run() |
任何其他工作都附加到gobject中的回调。
为什么要打扰线程和队列?
与readline()不同,BufferedReader.read1()不会阻塞等待 r n,如果有任何输出进入,它会返回ASAP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/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__() |
在我的情况下,我需要一个记录模块,它捕获后台应用程序的输出并增加它(添加时间戳,颜色等)。
我最终得到了一个后台线程来完成实际的I / O.以下代码仅适用于POSIX平台。我剥去了非必要的部分。
如果有人打算长期使用这种野兽考虑管理开放描述符。在我看来,这不是一个大问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | # -*- 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 = ' ' 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) |
选择模块可帮助您确定下一个有用输入的位置。
但是,对于单独的线程,你几乎总是更开心。一个阻塞读取stdin,另一个阻止读取你不想阻止的地方。
在现代Python中,事情要好得多。
这是一个简单的子程序,"hello.py":
1 2 3 4 5 6 7 | #!/usr/bin/env python3 while True: i = input() if i =="quit": break print(f"hello {i}") |
以及与之互动的程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 ") print(await proc.stdout.read(1024)) proc.stdin.write(b"alice ") print(await proc.stdout.read(1024)) proc.stdin.write(b"quit ") await proc.wait() asyncio.run(main()) |
打印出:
1 2 3 4 | b'hello bob ' b'hello alice ' |
请注意,实际模式(此处和相关问题中的几乎所有先前答案)都是将子项的stdout文件描述符设置为非阻塞,然后在某种选择循环中轮询它。当然,这些循环由asyncio提供。
我最近偶然发现了同样的问题
我需要从流中读取一行(在子进程中尾部运行)
在非阻塞模式下
我想避免下一个问题:不要刻录cpu,不要按一个字节读取流(比如readline就行)等等
这是我的实施
https://gist.github.com/grubberr/5501e1a9760c3eab5e0a
它不支持windows(poll),不处理EOF,
但它对我很有用
编辑:此实现仍然阻止。请改用J.F.Sebastian的答案。
我尝试了最佳答案,但线程代码的额外风险和维护令人担忧。
通过io模块(并限制为2.6),我找到了BufferedReader。这是我的无线,无阻塞的解决方案。 strike>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 ' ' 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 |
我根据J. F. Sebastian的解决方案创建了一个库。你可以使用它。
https://github.com/cenkalti/what
这是在子进程中运行交互式命令的示例,stdout是使用伪终端进行交互的。您可以参考:https://stackoverflow.com/a/43012138/3555925
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #!/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) |
我的问题有点不同,因为我想从正在运行的进程中收集stdout和stderr,但最终是相同的,因为我想在窗口小部件中生成输出。
我不想使用队列或其他线程来提出许多建议的解决方法,因为它们不需要执行诸如运行另一个脚本和收集其输出之类的常见任务。
在阅读了提出的解决方案和python文档后,我通过下面的实现解决了我的问题。是的它只适用于POSIX,因为我正在使用
我同意这些文档令人困惑,并且这种常见的脚本编写任务的实现很尴尬。我相信旧版本的python对
关键是将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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和VERBOSE只是将输出打印到终端的宏。
这个解决方案是IMHO 99.99%有效,因为它仍然使用阻塞
我欢迎反馈来改进解决方案,因为我还是Python新手。
此解决方案使用
鉴于它使用
该代码完全符合PEP8标准。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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 |
我也遇到了Jesse描述的问题并通过使用"选择"作为Bradley解决了它,Andy和其他人做了但是在阻止模式下避免了繁忙的循环。它使用虚拟管道作为假stdin。 select块并等待stdin或管道准备就绪。当按下某个键时,stdin取消阻塞选择,并且可以使用read(1)检索键值。当一个不同的线程写入管道时,管道解除阻塞选择,它可以作为stdin需求结束的指示。这是一些参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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() |
根据J.F. Sebastian的回答和其他几个来源,我已经整理了一个简单的子流程管理器。它提供请求非阻塞读取,以及并行运行多个进程。它不使用任何特定于操作系统的调用(我知道),因此应该在任何地方工作。
它可以从pypi获得,所以只需
这是一个支持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,而不会阻塞主线程。
请享用!