简体   繁体   English

Jupyter 笔记本中 Python 子进程的实时标准输出输出

[英]Live stdout output from Python subprocess in Jupyter notebook

I'm using subprocess to run a command line program from a Python (3.5.2) script, which I am running in a Jupyter notebook.我正在使用子进程从 Python (3.5.2) 脚本运行命令行程序,我在 Jupyter 笔记本中运行该脚本。 The subprocess takes a long time to run and so I would like its stdout to be printed live to the screen in the Jupyter notebook.子进程需要很长时间才能运行,因此我希望将其标准输出实时打印到 Jupyter 笔记本的屏幕上。

I can do this no problem in a normal Python script run from the terminal.在从终端运行的普通 Python 脚本中,我可以毫无问题地做到这一点。 I do this using:我这样做使用:

def run_command(cmd):
from subprocess import Popen, PIPE
import shlex

with Popen(shlex.split(cmd), stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')
    exit_code = p.poll()
return exit_code

However, when I run the script in a Jupyter notebook, it does not print the stdout live to the screen.但是,当我在 Jupyter 笔记本中运行脚本时,它不会将标准输出实时打印到屏幕上。 Instead, it prints everything after the subprocess has finished running.相反,它会在子进程完成运行后打印所有内容。

Does anyone have any ideas on how to remedy this?有没有人对如何解决这个问题有任何想法?

Many thanks, Johnny非常感谢,约翰尼

The ipython notebook has it's own support for running shell commands . ipython notebook 有自己的运行 shell 命令支持 If you don't need to capture with subprocess stuff you can just do如果你不需要用子进程的东西来捕获你可以做的

cmd = 'ls -l'
!{cmd}

Output from commands executed with !使用 ! 执行的命令的输出is automatically piped through the notebook.自动通过笔记本传输。

If you set stdout = None (this is the default, so you can omit the stdout argument altogether), then your process should write its output to the terminal running your IPython notebook server.如果您设置stdout = None (这是默认值,因此您可以完全省略stdout参数),那么您的进程应该将其输出写入运行您的 IPython 笔记本服务器的终端。

This happens because the default behavior is for subprocess to inherit from the parent file handlers (see docs ).发生这种情况是因为子进程的默认行为是从父文件处理程序继承的(请参阅文档)。

Your code would look like this:您的代码如下所示:

from subprocess import Popen, PIPE
import shlex

def run_command(cmd):
    p = Popen(shlex.split(cmd), bufsize=1, universal_newlines=True)
    return p.poll()

This won't print to the notebook in browser, but at least you will be able to see the output from your subprocess asynchronously while other code is running.这不会打印到浏览器中的笔记本,但至少您将能够在其他代码运行时异步查看子进程的输出。

Hope this helps.希望这会有所帮助。

Jupyter mucks with stdout and stderr. Jupyter 使用标准输出和标准错误。 This should get what you want, and give you a more useful exception when the command fails to boot.这应该会得到您想要的结果,并在命令无法启动时为您提供一个更有用的异常。

import signal
import subprocess as sp


class VerboseCalledProcessError(sp.CalledProcessError):
    def __str__(self):
        if self.returncode and self.returncode < 0:
            try:
                msg = "Command '%s' died with %r." % (
                    self.cmd, signal.Signals(-self.returncode))
            except ValueError:
                msg = "Command '%s' died with unknown signal %d." % (
                    self.cmd, -self.returncode)
        else:
            msg = "Command '%s' returned non-zero exit status %d." % (
                self.cmd, self.returncode)

        return f'{msg}\n' \
               f'Stdout:\n' \
               f'{self.output}\n' \
               f'Stderr:\n' \
               f'{self.stderr}'


def bash(cmd, print_stdout=True, print_stderr=True):
    proc = sp.Popen(cmd, stderr=sp.PIPE, stdout=sp.PIPE, shell=True, universal_newlines=True,
                    executable='/bin/bash')

    all_stdout = []
    all_stderr = []
    while proc.poll() is None:
        for stdout_line in proc.stdout:
            if stdout_line != '':
                if print_stdout:
                    print(stdout_line, end='')
                all_stdout.append(stdout_line)
        for stderr_line in proc.stderr:
            if stderr_line != '':
                if print_stderr:
                    print(stderr_line, end='', file=sys.stderr)
                all_stderr.append(stderr_line)

    stdout_text = ''.join(all_stdout)
    stderr_text = ''.join(all_stderr)
    if proc.wait() != 0:
        raise VerboseCalledProcessError(proc.returncode, cmd, stdout_text, stderr_text)

Replacing the for loop with the explicit readline() call worked for me.用显式readline()调用替换 for 循环对我readline()

from subprocess import Popen, PIPE
import shlex

def run_command(cmd):
    with Popen(shlex.split(cmd), stdout=PIPE, bufsize=1, universal_newlines=True) as p:
        while True:
            line = p.stdout.readline()
            if not line:
                break
            print(line)    
        exit_code = p.poll()
    return exit_code

Something is still broken about their iterators, even 4 years later.即使在 4 年后,他们的迭代器仍有一些问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM