代码之家  ›  专栏  ›  技术社区  ›  Mathieu Pagé

在python中的subprocess.pipe上进行非阻塞读取

  •  444
  • Mathieu Pagé  · 技术社区  · 17 年前

    我正在使用 subprocess module 启动子进程并连接到它的输出流(stdout)。我希望能够在其stdout上执行非阻塞读取。有没有一种方法可以使.readline不阻塞,或者在调用前检查流中是否有数据 .readline ?我希望它是可移植的,或者至少在Windows和Linux下工作。

    这是我目前的做法(它阻碍了 读行 如果没有可用的数据):

    p = subprocess.Popen('myprogram.exe', stdout = subprocess.PIPE)
    output_str = p.stdout.readline()
    
    26 回复  |  直到 7 年前
        1
  •  365
  •   ankostis    8 年前

    fcntl , select , asyncproc 在这种情况下不会有帮助。

    不管操作系统如何,读取流而不阻塞的可靠方法是使用 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
    
        2
  •  71
  •   Catskul    12 年前

    我经常遇到类似的问题;我编写的python程序经常需要能够执行一些主要功能,同时接受来自命令行(stdin)的用户输入。仅仅将用户输入处理功能放在另一个线程中并不能解决问题,因为 readline() 阻止并没有超时。如果主要功能已经完成,不再需要等待进一步的用户输入,我通常希望程序退出,但不能因为 RealLoad() 仍在另一个线程中阻塞,等待一行。我发现的解决此问题的方法是使用fcntl模块使stdin成为非阻塞文件:

    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)
    

    在我看来,这比使用select或signal模块来解决这个问题要干净一点,但是它只在unix上工作…

        3
  •  37
  •   Community Mohan Dere    9 年前

    python 3.4引入了新的 provisional API 对于异步IO-- asyncio module .

    方法类似于 twisted -based answer by @Bryan Ward --定义一个协议,一旦数据准备好就调用它的方法:

    #!/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()
    

    "Subprocess" in the docs .

    有一个高级接口 asyncio.create_subprocess_exec() 那回报 Process objects 允许使用 StreamReader.readline() coroutine async / await Python 3.5+ syntax ):

    #!/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() 执行以下任务:

    • 启动子进程,将其stdout重定向到管道
    • 异步从子进程的stdout读取行
    • 杀子过程
    • 等待它退出

    如有必要,每个步骤都可以被超时秒限制。

        4
  •  20
  •   Noah    15 年前

    试试 asyncproc 模块。例如:

    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的建议处理所有线程。

        5
  •  17
  •   Andy Jackson    15 年前

    使用“选择并读取”(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))
    

    对于readline()-类似:

    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
  •  17
  •   Bryan Ward    13 年前

    你可以很容易地做到这一点 Twisted . 根据您现有的代码库,这可能不是那么容易使用,但是如果您正在构建一个扭曲的应用程序,那么类似这样的事情就变得几乎微不足道了。你创造了一个 ProcessProtocol 类,并重写 outReceived() 方法。扭曲(取决于使用的反应堆)通常只是一个大 select() 安装回调以处理来自不同文件描述符(通常是网络套接字)的数据的循环。所以 超额收入() 方法只是安装一个回调来处理来自 STDOUT . 演示此行为的一个简单示例如下:

    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 documentation 有一些很好的信息。

    如果您围绕Twisted构建整个应用程序,那么它可以与本地或远程的其他进程进行异步通信,就像这样非常优雅。另一方面,如果你的程序不是建立在Twisted之上的,那么这就没有什么帮助了。希望这对其他读者有帮助,即使它不适用于您的特定应用程序。

        7
  •  8
  •   monkut    17 年前

    一种解决方案是让另一个进程执行对该进程的读取,或者使该进程的线程超时。

    以下是超时函数的线程版本:

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

    但是,当stdout进入时,您需要读取它吗? 另一种解决方案可能是将输出转储到文件,然后等待进程使用 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()
    
        8
  •  7
  •   Vukasin Toroman    14 年前

    免责声明:这只适用于龙卷风

    您可以通过将fd设置为非阻塞,然后使用ioloop注册回调来实现这一点。我把这个包装在一个叫 tornado_subprocess 您可以通过pypi安装它:

    easy_install tornado_subprocess
    

    现在您可以这样做:

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

    您还可以将它与请求处理程序一起使用

    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()
    
        9
  •  7
  •   Community Mohan Dere    9 年前

    现有的解决方案对我不起作用(详情如下)。最后的工作是使用read(1)实现readline(基于 this answer )后者不会阻止:

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

    现有解决方案不起作用的原因:

    1. 需要readline(包括基于队列的)的解决方案总是阻塞。这很困难(不可能?)终止执行readline的线程。它只在创建它的进程完成时被终止,而不是在输出生成进程被终止时。
    2. 正如anonn指出的那样,将低级fcntl与高级readline调用混合可能无法正常工作。
    3. 根据python文档,使用select.poll()很简单,但在Windows上不起作用。
    4. 对于这个任务,使用第三方库似乎是多余的,并且增加了额外的依赖项。
        10
  •  5
  •   Tom Lime    10 年前

    此版本的非阻塞读取 需要特殊的模块,并且将在大多数Linux DistOS上工作。

    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())
    
        11
  •  3
  •   Sebastien Claeys    15 年前

    我添加这个问题来阅读一些子过程。Popen stdout。 以下是我的非阻塞读取解决方案:

    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'
    
        12
  •  3
  •   datacompboy    13 年前

    这是我的代码,用于尽快捕获子流程的每个输出,包括部分行。它同时泵送,stdout和stderr的顺序几乎正确。

    在python 2.7linux&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__()
    
        13
  •  2
  •   Community Mohan Dere    9 年前

    在这里添加这个答案,因为它提供了在Windows和Unix上设置非阻塞管道的能力。

    所有的 ctypes 细节要感谢 @techtonik's answer .

    在Unix和Windows系统上都可以使用稍微修改过的版本。

    • python3兼容 (仅需细微改动) .
    • 包括POSIX版本,并定义要用于其中之一的异常。

    这样,您就可以对Unix和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: 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生成器(它返回每行的字节字符串)。

    它是一个发电机,所以你可以举个例子…

    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)
    
        14
  •  1
  •   S.Lott    17 年前

    这个 select 模块帮助您确定下一个有用的输入在哪里。

    然而,你几乎总是喜欢单独的线程。一个执行阻塞读取stdin,另一个执行不希望阻塞的任何位置。

        15
  •  1
  •   mfmain    11 年前

    为什么要打扰线程和队列? 与readline()不同,bufferedreader.read1()不会阻止等待\r\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__()
    
        16
  •  0
  •   Community Mohan Dere    9 年前

    我创建了一个基于 J. F. Sebastian's solution . 你可以用它。

    https://github.com/cenkalti/what

        17
  •  0
  •   edA-qa mort-ora-y    12 年前

    根据J.F.Sebastian的答案和其他一些信息来源,我构建了一个简单的子流程管理器。它提供请求非阻塞读取,以及并行运行多个进程。它不使用任何操作系统特定的调用(我知道),因此可以在任何地方工作。

    这是Pypi提供的,所以 pip install shelljob . 参考 project page 示例和完整文档。

        18
  •  0
  •   Community Mohan Dere    9 年前

    编辑:此实现仍然阻塞。使用J.F.Sebastian的 answer 相反。

    我试过了 top answer 但是线程代码的额外风险和维护是令人担忧的。

    透过 io module (仅限于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
    
        19
  •  0
  •   grubberr    11 年前

    我最近偶然发现了同样的问题 我需要一次从流中读取一行(子进程中的尾运行) 在非阻塞模式下 我想避免下一个问题:不烧掉CPU,不按一个字节读取流(就像readline一样),等等。

    这是我的实现 https://gist.github.com/grubberr/5501e1a9760c3eab5e0a 它不支持Windows(轮询),不处理EOF, 但它对我很有效

        20
  •  0
  •   Dmytro    11 年前

    在我的例子中,我需要一个日志记录模块来捕获后台应用程序的输出并对其进行扩充(添加时间戳、颜色等)。

    最后我得到了一个后台线程,它执行实际的I/O操作。下面的代码只适用于POSIX平台。我剥去了不重要的部分。

    如果有人打算长期使用这个beast,请考虑管理开放式描述符。就我而言,这不是一个大问题。

    # -*- 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)
    
        21
  •  0
  •   Community Mohan Dere    9 年前

    这是在子进程中运行交互式命令的示例,stdout使用伪终端进行交互。您可以参考: 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)
    
        22
  •  0
  •   brookbot    9 年前

    我的问题有点不同,因为我想从正在运行的进程中收集stdout和stderr,但最终还是一样的,因为我想在小部件中呈现生成的输出。

    我不想使用队列或其他线程来使用许多建议的解决方法,因为它们不需要执行诸如运行另一个脚本和收集其输出这样的公共任务。

    在阅读了建议的解决方案和python文档之后,我通过下面的实现解决了我的问题。是的,它只适用于posix,因为我正在使用 select 函数调用。

    我同意这些文档很混乱,而且对于这样一个常见的脚本任务,实现也很笨拙。我相信旧版本的python有不同的默认值 Popen 不同的解释导致了很多混乱。这似乎对Python2.7.12和3.5.2都很好。

    关键是要设定 bufsize=1 用于线路缓冲,然后 universal_newlines=True 作为文本文件而不是二进制文件进行处理,当设置 BufSead=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
    

    错误、调试和详细只是将输出打印到终端的宏。

    该解决方案是imho 99.99%有效的,因为它仍然使用阻塞 readline 函数,所以我们假设子进程是好的,并输出完整的行。

    我欢迎反馈来改进解决方案,因为我对python还是个新手。

        23
  •  0
  •   Bradley Odell    8 年前

    此解决方案使用 select 从IO流“读取任何可用数据”的模块。此函数最初会阻塞,直到数据可用,但随后只读取可用的数据,而不会进一步阻塞。

    考虑到它使用 选择 模块,这只在Unix上工作。

    代码完全符合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
    
        24
  •  0
  •   gonzaedu61    8 年前

    我还面临着 Jesse 并用“select”作为 Bradley , Andy 而其他人则是这样做的,只是为了避免繁忙的循环而采用阻塞模式。它使用一个虚拟管道作为假stdin。选择块并等待stdin或管道就绪。当按下某个键时,stdin取消阻止select,并且可以使用read(1)检索键值。当一个不同的线程写入管道时,管道将取消阻塞select,这可以作为stdin需求结束的指示。以下是一些参考代码:

    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()
    
        25
  •  -1
  •   cakan user6364254    7 年前

    我有原始发问者的问题,但不想调用线程。我将Jesse的解决方案与管道中的direct read()和我自己的缓存处理程序混合在一起进行行读取(但是,我的子进程ping总是写满行<一个系统页面大小)。我只在Gobject注册的IO表中阅读,以避免繁忙的等待。现在我通常在Gobject主循环中运行代码以避免线程。

    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
    

    观察者是

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

    主程序设置一个ping,然后调用gobject邮件循环。

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

    任何其他工作都与gobject中的回调有关。

        26
  •  -2
  •   Tim Savannah    10 年前

    下面是一个支持Python中非阻塞读和后台写的模块:

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

    提供一个函数,

    非块读取,它将从流中读取数据(如果可用),否则返回空字符串(如果流在另一侧关闭,并且所有可能的数据都已读取,则返回无)。

    您也可以考虑使用python-subprocess2模块,

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

    它添加到子流程模块中。因此,从“subprocess.popen”返回的对象被添加了一个额外的方法runinbackground。这将启动一个线程,并返回一个对象,该对象将自动填充为向stdout/stderr写入内容,而不会阻塞主线程。

    享受!

    推荐文章