V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
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 · 2020-04-23 10:18:10 +08:00 · 4981 次点击
    这是一个创建于 1709 天前的主题,其中的信息可能已经有所发展或是发生改变。

    脚本 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 条回复    2020-05-03 22:20:28 +08:00
    ipwx
        1
    ipwx  
       2020-04-23 10:19:56 +08:00   ❤️ 3
    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
        2
    ipwx  
       2020-04-23 10:21:34 +08:00
    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
        3
    MikuSama  
       2020-04-23 10:23:56 +08:00
    p = subprocess.call("python3 ./test1.py", shell=True)
    p.wait()

    建议:
    import test1
    MikuSama
        4
    MikuSama  
       2020-04-23 10:28:32 +08:00
    @MikuSama call 换成 Popen= =
    sunzy
        5
    sunzy  
    OP
       2020-04-23 10:31:26 +08:00
    @ipwx 大神太厉害了!在网上翻遍了资料都不行(哭~)。求问这是什么原因?
    sunzy
        6
    sunzy  
    OP
       2020-04-23 10:33:58 +08:00
    @ipwx 恩,我是要执行一个命令(ffmpeg -i %s -f flv %s -hide_banner),使用 list 命令列表总是报错,换成 shell=True 就好了
    ipwx
        7
    ipwx  
       2020-04-23 10:54:16 +08:00
    @sunzy 没啥特别的,Python 有个机制,检测输出设备是啥。如果不是交互式的 terminal,那么就会自动打开 python 自己的输出缓存。这和你调用 python 脚本的输入缓存无关,人家没有把数据推过来,你的调用者怎么做也是枉然。
    ipwx
        8
    ipwx  
       2020-04-23 10:54:33 +08:00
    PYTHONUNBUFFERED=1 这个环境变量能关掉这个机制。
    sunzy
        9
    sunzy  
    OP
       2020-04-23 11:13:15 +08:00
    @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
        10
    crella  
       2020-04-23 12:24:48 +08:00 via Android   ❤️ 1
    @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
        11
    ipwx  
       2020-04-23 13:40:17 +08:00
    @sunzy stderr=subprocess.STDOUT

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

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

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

    https://github.com/kkroening/ffmpeg-python
    sunzy
        14
    sunzy  
    OP
       2020-04-23 14:38:06 +08:00
    @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
        15
    sunzy  
    OP
       2020-04-23 14:39:00 +08:00
    @crella 多谢提醒!这个建议也很不错!
    ipwx
        16
    ipwx  
       2020-04-23 14:39:30 +08:00
    @sunzy 嗷原来你是 windows 啊。我一直在 linux 和 mac 下做,而且从来没用过 readlines,这倒是没注意到。
    Mohanson
        17
    Mohanson  
       2020-04-23 14:40:35 +08:00
    py 的 print 函数有缓存的, print(xxx, flush=True) 可以强制 flush
    sunzy
        18
    sunzy  
    OP
       2020-04-23 14:42:22 +08:00
    @ipwx 在 Mac 下开发,在 Linux 服务器上跑。 用 universal_newlines 这个参数是因为 ffmpeg 输出进度的时候用的是"\r"
    sunzy
        19
    sunzy  
    OP
       2020-04-23 14:43:44 +08:00
    @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
        20
    ipwx  
       2020-04-23 14:45:36 +08:00
    @sunzy 嘛嘛。我一般用 .read(n) 直接 stdout.buffer.write 这种,Popen 我还真没用过 readline
    ipwx
        21
    ipwx  
       2020-04-23 14:48:29 +08:00
    我知道了你是不是要处理 ffmpeg 的进度输出。那个没换行,所以要么是 \b 退格,要么是 \r 回到行首。所以没有 \n 你之前用 readline 搞不定很正常。像我一般用 .read 手动处理界定符,真注意不到这个。
    mathzhaoliang
        22
    mathzhaoliang  
       2020-04-23 14:53:48 +08:00
    我会这样写

    ```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"))
    ```
    FaiChou
        23
    FaiChou  
       2020-04-23 15:10:51 +08:00
    应该是相同的问题,我在 node 中遇到过 https://www.v2ex.com/t/645816#reply2
    sunzy
        24
    sunzy  
    OP
       2020-04-23 15:20:14 +08:00
    @ipwx 是的,是要监控进度输出,用.read(n)怎么读取正好一行的数据呢?
    sunzy
        25
    sunzy  
    OP
       2020-04-23 15:32:01 +08:00
    @mathzhaoliang 这样写只能获取最后的信息吧
    ipwx
        26
    ipwx  
       2020-04-23 20:46:52 +08:00
    @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
        27
    ipwx  
       2020-04-23 20:47:30 +08:00
    就是用个缓冲区自己解析一下。上面是伪代码,不是真的能运行的代码。
    sunzy
        28
    sunzy  
    OP
       2020-04-24 09:45:42 +08:00
    @ipwx 赞!
    yyfeng88625
        29
    yyfeng88625  
       2020-04-24 12:08:35 +08:00   ❤️ 1
    使用 sh 库就好,自己写里面很多坑的
    https://amoffat.github.io/sh/tutorials/real_time_output.html
    sunzy
        30
    sunzy  
    OP
       2020-05-03 22:20:28 +08:00
    @yyfeng88625 这个库不错,学习了,感谢!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5421 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 40ms · UTC 08:26 · PVG 16:26 · LAX 00:26 · JFK 03:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.