如何在Python中保持subprocess的stdout与stderr重定向顺序?
交错输出的模拟脚本
我编写了一个模拟程序交替写入stdout和stderr的脚本:
import sys import time for i in range(5): print(int(time.time()), "This is Stdout") print(int(time.time()), "Stderr", file=sys.stderr) time.sleep(1)
重定向后的顺序混乱问题
使用Python的subprocess.Popen将输出重定向到同一文件时,stdout和stderr的输出顺序会混乱——所有stderr内容都排在stdout之前:
import subprocess file = open('./stdout1.log', 'w', encoding='utf-8') subprocess.Popen('./stderrout.py', stdout=file, stderr=file)
我尝试过使用subprocess.STDOUT、调整text和bufsize参数、修改文件句柄的buffering,但所有测试场景下的输出顺序均一致。
完整测试代码
import subprocess file = open('./stdout1.log', 'w', encoding='utf-8') ; subprocess.Popen('./stderrout.py', stdout=file, stderr=file) file = open('./stdout2.log', 'w', encoding='utf-8') ; subprocess.Popen('./stderrout.py', stdout=file, stderr=file, text=True, bufsize=0) file = open('./stdout3.log', 'w', encoding='utf-8') ; subprocess.Popen('./stderrout.py', stdout=file, stderr=file, text=True, bufsize=1) file = open('./stdout4.log', 'w', encoding='utf-8') ; subprocess.Popen('./stderrout.py', stdout=file, stderr=subprocess.STDOUT) file = open('./stdout5.log', 'w', encoding='utf-8') ; subprocess.Popen('./stderrout.py', stdout=file, stderr=subprocess.STDOUT, text=True, bufsize=0) file = open('./stdout6.log', 'w', encoding='utf-8') ; subprocess.Popen('./stderrout.py', stdout=file, stderr=subprocess.STDOUT, text=True, bufsize=1) file = open('./stdout1b.log', 'w', encoding='utf-8', buffering=1) ; subprocess.Popen('./stderrout.py', stdout=file, stderr=file) file = open('./stdout2b.log', 'w', encoding='utf-8', buffering=1) ; subprocess.Popen('./stderrout.py', stdout=file, stderr=file, text=True, bufsize=0) file = open('./stdout3b.log', 'w', encoding='utf-8', buffering=1) ; subprocess.Popen('./stderrout.py', stdout=file, stderr=file, text=True, bufsize=1) file = open('./stdout4b.log', 'w', encoding='utf-8', buffering=1) ; subprocess.Popen('./stderrout.py', stdout=file, stderr=subprocess.STDOUT) file = open('./stdout5b.log', 'w', encoding='utf-8', buffering=1) ; subprocess.Popen('./stderrout.py', stdout=file, stderr=subprocess.STDOUT, text=True, bufsize=0) file = open('./stdout6b.log', 'w', encoding='utf-8', buffering=1) ; subprocess.Popen('./stderrout.py', stdout=file, stderr=subprocess.STDOUT, text=True, bufsize=1)
提问
如何将两个流重定向到同一文件且不丢失输出顺序?若无法实现,bufsize参数的意义是什么?
保持输出顺序的方法
要维持子进程的输出顺序,核心是消除子进程自身的缓冲差异,或通过父进程同步处理两个流:
修改子进程的缓冲设置
在模拟脚本中强制关闭stdout和stderr的缓冲,让输出实时写入:import sys import time # 关闭标准输出和错误输出的缓冲 sys.stdout = open(sys.stdout.fileno(), 'w', buffering=0) sys.stderr = open(sys.stderr.fileno(), 'w', buffering=0) for i in range(5): print(int(time.time()), "This is Stdout") print(int(time.time()), "Stderr", file=sys.stderr) time.sleep(1)Python中
stdout重定向到文件时默认是全缓冲,而stderr默认无缓冲,关闭缓冲后两者都会实时输出,不会出现顺序错位。父进程通过管道同步读取写入
父进程分别捕获两个流的管道,用线程实时读取并写入文件,保证顺序:import subprocess import threading def read_and_write(stream, output_file): for line in iter(stream.readline, ''): output_file.write(line) output_file.flush() with open('./combined.log', 'w', encoding='utf-8') as f: proc = subprocess.Popen( './stderrout.py', stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) # 启动线程分别处理两个流 stdout_thread = threading.Thread(target=read_and_write, args=(proc.stdout, f)) stderr_thread = threading.Thread(target=read_and_write, args=(proc.stderr, f)) stdout_thread.start() stderr_thread.start() # 等待线程和子进程结束 stdout_thread.join() stderr_thread.join() proc.wait()这种方式不受子进程缓冲设置影响,父进程会实时获取输出并写入,严格保持顺序。
结合
subprocess.STDOUT与子进程无缓冲
使用stderr=subprocess.STDOUT将两个流合并,同时让子进程输出无缓冲,也能保证顺序,但前提是子进程本身不做缓冲。
bufsize参数的意义
bufsize是控制父进程与子进程之间管道缓冲的参数,不同取值的作用:
bufsize=0:无缓冲,父进程每次读取都会直接获取子进程的输出,不做缓存。bufsize=1:行缓冲,仅在文本模式(text=True)下生效,按行缓冲输出内容。bufsize=-1:使用系统默认缓冲大小(默认值),通常为全缓冲。- 正数
bufsize=N:设置固定大小的字节缓冲区,缓冲区满时才会触发读取。
需要注意的是:bufsize只控制父进程端的管道缓冲,你遇到的顺序问题核心原因是子进程自身的缓冲差异——stdout重定向到文件时默认全缓冲,而stderr默认无缓冲,导致stderr内容先被写入文件。
内容的提问来源于stack exchange,提问作者Krishna

