推荐学习书目
› Learn Python the Hard Way
Python Sites
› PyPI - Python Package Index
› http://diveintopython.org/toc/index.html
› Pocoo
值得关注的项目
› PyPy
› Celery
› Jinja2
› Read the Docs
› gevent
› pyenv
› virtualenv
› Stackless Python
› Beautiful Soup
› 结巴中文分词
› Green Unicorn
› Sentry
› Shovel
› Pyflakes
› pytest
Python 编程
› pep8 Checker
Styles
› PEP 8
› Google Python Style Guide
› Code Style from The Hitchhiker's Guide
sunzy
V2EX  ›  Python

使用 subprocess 执行另一个脚本,如何实时输出?

  •  
  •   sunzy · Apr 23, 2020 · 6268 views
    This topic created in 2388 days ago, the information mentioned may be changed or developed.

    脚本 1,负责输出

    
    for i in range(1, 10):
        print(i)
        time.sleep(1)
    

    脚本 2,用 subprocess 执行脚本 1,获取 stdout 并输出

    import subprocess
    
    proc = subprocess.Popen("python3 ./test1.py", shell=True, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    while True:
      line = proc.stdout.readline()
      if not line:
        break
      print(line.rstrip())
    

    期望的是实时输出,结果是一直等到脚本 1 执行完了才整体输出所有的内容

    stackoverflow 上有一篇Non-blocking read on a subprocess.PIPE in python ,我试了线程和 fcntl,还是原样

    本人小白,求大神~~

    30 replies  •  2020-05-03 22:20:28 +08:00
    ipwx
    ipwx
    Apr 23, 2020❤️3
    1
    env = os.environ.copy()
    env['PYTHONUNBUFFERED'] = '1'
    subprocess.Popen("python3 ./test1.py", shell=True, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
    ipwx
    ipwx
    Apr 23, 2020
    2
    emmmm 重点是 env=env

    另外如果是我的话,不会使用 shell=True,也不会设置 bufsize 。我的话会这样:

    env = os.environ.copy()
    env['PYTHONUNBUFFERED'] = '1'
    subprocess.Popen([sys.executable, ...], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
    MikuSama
    MikuSama
    Apr 23, 2020
    3
    p = subprocess.call("python3 ./test1.py", shell=True)
    p.wait()

    建议:
    import test1
    MikuSama
    MikuSama
    Apr 23, 2020
    4
    @MikuSama call 换成 Popen= =
    sunzy
    sunzy
    OP
    Apr 23, 2020
    5
    @ipwx 大神太厉害了!在网上翻遍了资料都不行(哭~)。求问这是什么原因?
    sunzy
    sunzy
    OP
    Apr 23, 2020
    6
    @ipwx 恩,我是要执行一个命令(ffmpeg -i %s -f flv %s -hide_banner),使用 list 命令列表总是报错,换成 shell=True 就好了
    ipwx
    ipwx
    Apr 23, 2020
    7
    @sunzy 没啥特别的,Python 有个机制,检测输出设备是啥。如果不是交互式的 terminal,那么就会自动打开 python 自己的输出缓存。这和你调用 python 脚本的输入缓存无关,人家没有把数据推过来,你的调用者怎么做也是枉然。
    ipwx
    ipwx
    Apr 23, 2020
    8
    PYTHONUNBUFFERED=1 这个环境变量能关掉这个机制。
    sunzy
    sunzy
    OP
    Apr 23, 2020
    9
    @ipwx 这个测试脚本跑通了,但是换成 ffpmeg 就不行了,汗~

    ```python
    env = os.environ.copy()
    env['PYTHONUNBUFFERED'] = '1'

    r = subprocess.Popen("ffmpeg -i %s -f flv %s -hide_banner" % (source, target), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
    while True:
    line = r.stdout.readline()
    line2 = r.stderr.readline()
    if not line and not line2 and r.poll() != None:
    break
    print(line.rstrip())
    print(line2.rstrip())
    ```
    crella
    crella
    Apr 23, 2020 via Android❤️1
    10
    @sunzy 跨语言访问终端输出流很容易出现意料之外的结果。

    建议 ffmpeg 结合平台,把 stdout 指向文件 A,然后 python 定时读取文件 A 的最后一行。

    windows 上的 cmd 有 >file.txt 输出 stdout 和 2>file.txt 输出 stderr 。ffmpeg 有些信息从 stdout 出,有些信息从 stderr 出。


    首先我用 c#读写 ruby 的 stdout 折腾了好久;然后 c#与 ruby 通过 win socket 沟通,一直没搞好……
    ipwx
    ipwx
    Apr 23, 2020
    11
    @sunzy stderr=subprocess.STDOUT

    你这最大的问题就是这两句:

    line = r.stdout.readline()
    line2 = r.stderr.readline()

    想象一下,如果被调用的程序只通过一个 stdout 输出,那么你在第二行就会一直 hang,直到程序退出。反过来,如果被调用的程序只通过 stderr 输出,那你的程序就会在第一行 hang,直到程序退出。所以无论什么时候,这两行这么写都是不对的。除非你开两个线程后台读取。
    ipwx
    ipwx
    Apr 23, 2020
    12
    @sunzy 所以用 subprocess.Popen(..., stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 最最方便的选择。
    ipwx
    ipwx
    Apr 23, 2020
    13
    另外 ffmpeg 的话,你可以考虑用库,比如:

    https://github.com/kkroening/ffmpeg-python
    sunzy
    sunzy
    OP
    Apr 23, 2020
    14
    @ipwx 多谢!

    找到了 universal_newlines 这个参数,可以完全满足我的需求!

    r = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env,universal_newlines=True)
    while True:
    line = r.stdout.readline()
    if not line and r.poll() != None:
    break
    print(line.rstrip())

    先不折腾 ffmpeg-python 库了(看了一下,还是要学习成本了),等这个忙完了再看吧
    sunzy
    sunzy
    OP
    Apr 23, 2020
    15
    @crella 多谢提醒!这个建议也很不错!
    ipwx
    ipwx
    Apr 23, 2020
    16
    @sunzy 嗷原来你是 windows 啊。我一直在 linux 和 mac 下做,而且从来没用过 readlines,这倒是没注意到。
    Mohanson
    Mohanson
    Apr 23, 2020
    17
    py 的 print 函数有缓存的, print(xxx, flush=True) 可以强制 flush
    sunzy
    sunzy
    OP
    Apr 23, 2020
    18
    @ipwx 在 Mac 下开发,在 Linux 服务器上跑。 用 universal_newlines 这个参数是因为 ffmpeg 输出进度的时候用的是"\r"
    sunzy
    sunzy
    OP
    Apr 23, 2020
    19
    @ipwx "The trick is to add universal_newlines=True to the subprocess.Popen() call, because ffmpeg's output is in fact unbuffered but comes with newline-characters" ---stackoverflow
    ipwx
    ipwx
    Apr 23, 2020
    20
    @sunzy 嘛嘛。我一般用 .read(n) 直接 stdout.buffer.write 这种,Popen 我还真没用过 readline
    ipwx
    ipwx
    Apr 23, 2020
    21
    我知道了你是不是要处理 ffmpeg 的进度输出。那个没换行,所以要么是 \b 退格,要么是 \r 回到行首。所以没有 \n 你之前用 readline 搞不定很正常。像我一般用 .read 手动处理界定符,真注意不到这个。
    mathzhaoliang
    mathzhaoliang
    Apr 23, 2020
    22
    我会这样写

    ```python
    process = subprocess.Popen(
    ffmpeg_command,
    shell=True,
    stderr=subprocess.PIPE,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE)

    _, err = process.communicate()
    if process.returncode:
    print(type(err), err)
    raise IOError("FFMPEG error: " + err.decode("ascii"))
    ```
    ethusdt
    ethusdt
    Apr 23, 2020
    23
    应该是相同的问题,我在 node 中遇到过 https://www.v2ex.com/t/645816#reply2
    sunzy
    sunzy
    OP
    Apr 23, 2020
    24
    @ipwx 是的,是要监控进度输出,用.read(n)怎么读取正好一行的数据呢?
    sunzy
    sunzy
    OP
    Apr 23, 2020
    25
    @mathzhaoliang 这样写只能获取最后的信息吧
    ipwx
    ipwx
    Apr 23, 2020
    26
    @sunzy 基本思路:

    buf = b''

    def handle(cnt):
    ....buf += cnt
    ....while True:
    ........pos = buf.find(b'\n‘)
    ........if pos <= -1:
    ............break
    ........handle_line(buf[:pos])
    ........buf = buf[pos:]

    def handle_line(line):
    ....do what you want to do

    while not eof:
    ....handle(read(n))
    if buf:
    ....handle_line(buf)
    ipwx
    ipwx
    Apr 23, 2020
    27
    就是用个缓冲区自己解析一下。上面是伪代码,不是真的能运行的代码。
    sunzy
    sunzy
    OP
    Apr 24, 2020
    28
    @ipwx 赞!
    yyfeng88625
    yyfeng88625
    Apr 24, 2020❤️1
    29
    使用 sh 库就好,自己写里面很多坑的
    https://amoffat.github.io/sh/tutorials/real_time_output.html
    sunzy
    sunzy
    OP
    May 3, 2020
    30
    @yyfeng88625 这个库不错,学习了,感谢!
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   4197 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 49ms · UTC 04:03 · PVG 12:03 · LAX 20:03 · JFK 23:03
    ♥ Do have faith in what you're doing.