You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python subprocess调用Shell脚本时控制台无法实时打印输出的问题

解决subprocess调用shell脚本时子进程输出不实时的问题

嘿,我完全懂你遇到的这个坑!我之前用subprocess调用批量脚本的时候也碰到过一模一样的情况——明明子进程在跑,但控制台就是没输出,非得等所有进程都跑完才一次性把内容吐出来,哪怕加了set -x或者手动读管道都没用。

先把你的场景再理清楚:

  • 你的Python调用脚本(test.py)是这样写的:
    import subprocess
    
    sub = subprocess.Popen(["sh", "test.sh"])
    sub.wait()
    
  • 你的test.sh里会调用多个Python脚本,但这些子进程的输出不会实时显示在控制台。
  • 你试过在test.sh里加set -x,或者改成下面的Python代码,结果还是所有输出一次性打印:
    import subprocess
    import sys
    
    sub = subprocess.Popen(["sh", "test.sh"], shell=False,
                          stdout=subprocess.PIPE ,stderr=subprocess.PIPE)
    while sub.poll() == None or sub.poll() != 0:
        print(sub.stdout.readline())
        sub.stdout.flush()
        sys.stdout.flush()
    

问题根源:输出缓冲

其实这是因为系统的输出缓冲机制在搞鬼:当进程的输出不是直接连接到终端(比如被subprocess的管道捕获,或者脚本里的子进程输出到非终端环境),系统会启用「块缓冲」——也就是攒够一大块数据才会输出,而不是有一行就输出一行。哪怕你手动flush,也绕不开底层的缓冲设置。

几个可行的解决方案

1. 让脚本里的Python子进程禁用缓冲

最简单的办法就是给test.sh里调用的每个Python命令加上-u参数,这个参数会让Python禁用标准输出的缓冲,强制行输出:

# 在test.sh里改成这样调用Python脚本
python -u script1.py
python -u script2.py

或者更省心一点,在test.sh的开头加一行,全局设置Python无缓冲:

export PYTHONUNBUFFERED=1
# 后面正常调用Python脚本就行
python script1.py
python script2.py

2. 让subprocess直接继承当前终端的输出

如果你不需要在Python里处理子进程的输出,只是想让它实时显示在控制台,那完全不用搞管道,直接让subprocess继承当前进程的stdout和stderr就行:

import subprocess
import sys

sub = subprocess.Popen(["sh", "test.sh"], stdout=sys.stdout, stderr=sys.stderr)
sub.wait()

Python 3.5+的话,用subprocess.run更简洁,默认就是继承输出:

import subprocess

subprocess.run(["sh", "test.sh"])

这样子进程的输出会直接连到终端,系统会自动用行缓冲,就能实时看到输出了。

3. 必须用管道读取?用线程避免死锁+实时读取

如果你的需求是要在Python里处理子进程的输出,那之前的循环逻辑有问题——当sub.poll() !=0的时候进程已经结束了,这时候readline可能会卡住,而且只处理stdout会忽略stderr,容易导致死锁。

可以用线程分别读取stdout和stderr,这样能实时拿到每一行输出:

import subprocess
import sys
from threading import Thread

def read_stream(stream, print_func):
    # 迭代读取每一行,直到流结束
    for line in iter(stream.readline, ''):
        print_func(line.strip())
        sys.stdout.flush()

# text=True让输出直接是字符串,不用手动解码
sub = subprocess.Popen(["sh", "test.sh"], 
                       stdout=subprocess.PIPE, 
                       stderr=subprocess.PIPE,
                       text=True)

# 启动线程分别处理stdout和stderr
stdout_thread = Thread(target=read_stream, args=(sub.stdout, print))
# 把stderr输出到标准错误流
stderr_thread = Thread(target=read_stream, args=(sub.stderr, lambda x: print(x, file=sys.stderr)))

stdout_thread.start()
stderr_thread.start()

# 等待线程结束
stdout_thread.join()
stderr_thread.join()
# 等待子进程结束
sub.wait()

这个方法用线程分开处理两个流,避免了因为其中一个流满了导致进程阻塞的问题,同时能实时输出每一行内容。

内容的提问来源于stack exchange,提问作者MIEE IEEE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:19:58