当前位置: 首页 > 面试题库 >

运行命令并像在终端中一样近实时地分别获取其stdout,stderr

易京
2023-03-14
问题内容

我正在尝试在Python中找到一种方法来运行其他程序,使得:

  1. 可以分别记录正在运行的程序的stdout和stderr。
  2. 可以实时查看正在运行的程序的stdout和stderr,这样,如果子进程挂起,则用户可以看到。(即,我们不等待执行完成再将stdout / stderr打印给用户)
  3. 奖励标准:正在运行的程序不知道它是通过python运行的,因此不会做意外的事情(例如,将其输出分块而不是实时打印,或者退出,因为它需要一个终端来查看其输出) 。这个小的标准几乎意味着我们将需要使用pty。

这是我到目前为止所得到的…方法1:

def method1(command):
    ## subprocess.communicate() will give us the stdout and stderr sepurately, 
    ## but we will have to wait until the end of command execution to print anything.
    ## This means if the child process hangs, we will never know....
    proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
    stdout, stderr = proc.communicate() # record both, but no way to print stdout/stderr in real-time
    print ' ######### REAL-TIME ######### '
    ########         Not Possible
    print ' ########## RESULTS ########## '
    print 'STDOUT:'
    print stdout
    print 'STDOUT:'
    print stderr

方法2

def method2(command):
    ## Using pexpect to run our command in a pty, we can see the child's stdout in real-time,
    ## however we cannot see the stderr from "curl google.com", presumably because it is not connected to a pty?
    ## Furthermore, I do not know how to log it beyond writing out to a file (p.logfile). I need the stdout and stderr
    ## as strings, not files on disk! On the upside, pexpect would give alot of extra functionality (if it worked!)
    proc = pexpect.spawn('/bin/bash', ['-c', command])
    print ' ######### REAL-TIME ######### '
    proc.interact()
    print ' ########## RESULTS ########## '
    ########         Not Possible

方法3:

def method3(command):
    ## This method is very much like method1, and would work exactly as desired
    ## if only proc.xxx.read(1) wouldn't block waiting for something. Which it does. So this is useless.
    proc=subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, executable='/bin/bash')
    print ' ######### REAL-TIME ######### '
    out,err,outbuf,errbuf = '','','',''
    firstToSpeak = None
    while proc.poll() == None:
            stdout = proc.stdout.read(1) # blocks
            stderr = proc.stderr.read(1) # also blocks
            if firstToSpeak == None:
                if stdout != '': firstToSpeak = 'stdout'; outbuf,errbuf = stdout,stderr
                elif stderr != '': firstToSpeak = 'stderr'; outbuf,errbuf = stdout,stderr
            else:
                if (stdout != '') or (stderr != ''): outbuf += stdout; errbuf += stderr
                else:
                    out += outbuf; err += errbuf;
                    if firstToSpeak == 'stdout': sys.stdout.write(outbuf+errbuf);sys.stdout.flush()
                    else: sys.stdout.write(errbuf+outbuf);sys.stdout.flush()
                    firstToSpeak = None
    print ''
    print ' ########## RESULTS ########## '
    print 'STDOUT:'
    print out
    print 'STDERR:'
    print err

要尝试这些方法,您将需要 import sys,subprocess,pexpect

pexpect是纯Python,可以与

sudo pip安装pexpect

我认为解决方案将涉及python的pty模块-这有点像是一种妖术,我找不到任何会使用的人。也许这样就知道了:)作为一个提示,我建议您使用“ curl
www.google.com”作为测试命令,因为出于某种原因它会在stderr上显示其状态:D

UPDATE-1:
好的,因此pty库不适合人类使用。这些文档本质上是源代码。呈现的任何阻塞且非异步的解决方案在这里都无法正常工作。尽管无法添加pty支持,但是Padraic
Cunningham的Threads /
Queue方法效果很好,并且它是“肮脏的”(引用Freenode的#python)。似乎唯一适合生产标准代码的解决方案是使用Twisted框架,该框架甚至支持pty作为布尔型开关来运行过程,就好像它们是从shell调用的一样。但是将Twisted添加到项目中需要完全重写所有代码。这真是太可惜了:/

UPDATE-2:

提供了两个答案,其中一个解决了前两个条件,将在您只需要使用stdout和stderr的地方很好地工作Threads and Queue。另一个答案使用select一种非阻塞方法(用于读取文件描述符)和pty(一种“欺骗”所产生的进程,使其认为它在真实终端中运行,就像直接在Bash中运行一样),但可能会没有副作用。我希望我能接受两个答案,因为“正确”的方法确实取决于情况以及为什么要首先进行子处理,但是,a,我只能接受一个。


问题答案:

可以分别记录正在运行的程序的stdout和stderr。

您无法使用,pexpect因为stdout和stderr都使用相同的pty方法,之后无法将它们分开。

可以实时查看正在运行的程序的stdout和stderr,这样,如果子进程挂起,则用户可以看到。(即,我们不等待执行完成再将stdout /
stderr打印给用户)

如果子进程的输出不是tty
,则可能使用块缓冲,因此,如果子进程的输出不大
,则不会是“实时”,
例如,如果缓冲区为4K,则您的父级直到子进程打印4K字符并且缓冲区溢出或显式刷新(在子进程内部),Python进程才会看到任何内容。该缓冲区位于子进程内部,没有标准的方法可以从外部进行管理。这是显示stdio缓冲区和command 1 | command2shell管道的管道缓冲区的图片:

管道/
stdio缓冲区

正在运行的程序不知道它是通过python运行的,因此不会做意外的事情(例如,将其输出分块而不是实时打印,或者退出,因为它需要一个终端来查看其输出)。

看来,您的意思相反,即,如果将输出重定向到管道(stdout=PIPE在Python中使用),则子进程可能会对其输出进行分块,而不是尽快刷新每条输出行。这意味着默认的线程或异步解决方案将无法正常使用。

有几种解决方法:

  • 该命令可以接受诸如grep --line-buffered或的命令行参数python -u来禁用块缓冲。

  • stdbuf适用于某些程序,即,您可以['stdbuf', '-oL', '-eL'] + command使用上面的线程或asyncio解决方案运行,并且应该分别获得stdout,stderr和行,并且这些行应几乎实时显示:

    #!/usr/bin/env python3
    

    import os
    import sys
    from select import select
    from subprocess import Popen, PIPE

    with Popen([‘stdbuf’, ‘-oL’, ‘-e0’, ‘curl’, 'www.google.com’],
    stdout=PIPE, stderr=PIPE) as p:
    readable = {
    p.stdout.fileno(): sys.stdout.buffer, # log separately
    p.stderr.fileno(): sys.stderr.buffer,
    }
    while readable:
    for fd in select(readable, [], [])[0]:
    data = os.read(fd, 1024) # read available
    if not data: # EOF
    del readable[fd]
    else:
    readable[fd].write(data)
    readable[fd].flush()

  • 最后,您可以尝试使用pty+select两个解决方案pty

    #!/usr/bin/env python3
    

    import errno
    import os
    import pty
    import sys
    from select import select
    from subprocess import Popen

    masters, slaves = zip(pty.openpty(), pty.openpty())
    with Popen([sys.executable, ‘-c’, r’‘’import sys, time
    print(‘stdout’, 1) # no explicit flush
    time.sleep(.5)
    print(‘stderr’, 2, file=sys.stderr)
    time.sleep(.5)
    print(‘stdout’, 3)
    time.sleep(.5)
    print(‘stderr’, 4, file=sys.stderr)
    ‘’‘],
    stdin=slaves[0], stdout=slaves[0], stderr=slaves[1]):
    for fd in slaves:
    os.close(fd) # no input
    readable = {
    masters[0]: sys.stdout.buffer, # log separately
    masters[1]: sys.stderr.buffer,
    }
    while readable:
    for fd in select(readable, [], [])[0]:
    try:
    data = os.read(fd, 1024) # read available
    except OSError as e:
    if e.errno != errno.EIO:
    raise #XXX cleanup
    del readable[fd] # EIO means EOF on some systems
    else:
    if not data: # EOF
    del readable[fd]
    else:
    readable[fd].write(data)
    readable[fd].flush()
    for fd in masters:
    os.close(fd)

我不知道pty对stdout,stderr使用不同的s有什么副作用。您可以尝试在您的情况下stderr=PIPE使用一个pty是否足够,例如,设置并使用p.stderr.fileno()代替masters[1]。来源中的评论sh表明,如果stderr not in {STDOUT, pipe}



 类似资料:
  • 问题内容: 我想运行一些命令,直到按Ctrl-C才会退出。是否可以运行一次即可运行所有这些程序,而Ctrl-C会全部退出它们?他们可以共享终端输出。 具体来说,我有罗盘编译器,coffeescript编译器和一个自定义命令,用于监视文件更改,所有命令都在运行以监视文件更改。我不想为每个命令加载一个终端。 问题答案: 该bash脚本适用于N个并行线程。每个参数都是一个命令。 捕获SIGINT时将杀死

  • 我有一个程序,当您双击它时会运行。我想做的是: 双击 jar 文件 (只是一个例子,这不是我想做的) 我试过这个: (< code>SOME_COMMAND_HERE只是一个替换) 它没有启动终端。 (我有GUI部分,所以不需要为此编写代码) 那么我该怎么做呢?在过去的一两天里,我一直在寻找堆栈溢出的答案,以及激烈的谷歌搜索。另外,我需要它的原因是因为我想在运行sudo命令时向用户显示一些进度并输

  • 问题内容: 关于主题,下面的代码 从Ubuntu运行终端。 运行终端后如何在终端中发出命令? 例如:运行终端并运行命令,例如“ ls”等。 问题答案: 您可以在命令行中提供一些选项,以指定执行的内容。 该选项为您提供大致相同的好处,但您可以将命令行拆分为单独的单词。 二者并同时连接program`s标准输入和输出到端子与可选参数运行该程序。因此,用户可以与终端进行正确的交互。 例: 这将打开终端并

  • 我想做的是在我的控制台中运行命令,但我想知道如何为控制台中的命令创建一个监听器。 但是在一个同样适用于控制台的版本中,有没有人知道如何做到这一点呢?

  • 问题内容: 我试图打开一个终端并在其中运行命令。我在用 这将打开一个新的终端,但是命令不会执行。 问题答案: 该命令将新运行的进程替换为新进程,因此,如果要在要运行的命令列表中有一个exec,则在exec运行后,其他任何命令都将不会运行。因此,您要替换为,然后在运行之后再执行任何操作。尝试以下方法: 或者如果您需要终端保持打开状态,请尝试以下操作: 如果您希望终端保持打开状态并处于bash she

  • 请先参考这个问题, 无法读取opensslv. h:没有这样的文件或目录 基于此,我需要使用AppleScript运行以下三行终端命令, 我尝试了两种方法,我创建了带有.命令和. sh扩展名的文本文件,并添加了上述三行。然后尝试从AppleScript运行它, 但我犯了这个错误, 这可能行得通, 但它在第三行的终端上请求密码,并等待用户响应。AppleScript中显示的密码对话框(当以管理员权限