stdout=Subprocess.PIPE与stdout=None输出时序差异问题求助
问题
我正在为steamcmd(一款从Steam创意工坊下载模组的命令行工具)开发实时包装器。单独运行steamcmd,或通过subprocess.Popen(args, stdout=None, creationflags=subprocess.CREATE_NEW_CONSOLE)启动时,输出会实时显示(比如每下载完一个创意工坊项目后,就会打印该项目的ID)。但当使用subprocess.Popen(args, stdout=subprocess.PIPE, creationflags=subprocess.CREATE_NEW_CONSOLE)(或其他creationflags)启动进程时,只能一次性获取大块输出,且输出总会卡在“Waiting for user info...”结尾,直到所有项目下载完成才会输出全部内容。
我用以下代码读取stdout:
while True: out = process.stdout.read1() print(out.decode("utf-8", errors="ignore"), end="", flush=True) return_code = process.poll() if return_code is not None: break
我还尝试过文本模式下的stdout.read(1)(每次读取单个字符),甚至stdout.readline(因为输出块包含多行),但所有方法都存在相同的“成块输出”问题。
将输出重定向到文件并使用tail -f查看时,输出同样是“成块”的——这似乎是“控制台输出”与“管道输出”之间的普遍差异。如何让Popen像连接到控制台窗口那样接收steamcmd的输出?
示例命令行:
steamcmd.exe +login anonymous +workshop_download_item 281990 899653076 +workshop_download_item 281990 1596617351 +workshop_download_item 281990 1638580371 +workshop_download_item 281990 1724144458
解决方案
这个问题的本质是steamcmd会根据输出目标调整缓冲策略:输出到控制台时用行缓冲/无缓冲,实时输出内容;输出到管道或文件时用全缓冲,积累到一定大小才一次性输出。核心解决思路是让steamcmd误以为自己连接的是控制台而非管道,以下是几种可行方案:
1. 使用WinPTY模拟终端环境(Windows平台)
WinPTY可以为命令行程序模拟伪终端,让steamcmd识别为控制台环境,从而实时输出。先安装pywinpty库:
pip install pywinpty
然后用以下代码调用steamcmd:
import winpty # 创建伪终端并启动steamcmd process = winpty.PtySpawn([ "steamcmd.exe", "+login", "anonymous", "+workshop_download_item", "281990", "899653076", "+workshop_download_item", "281990", "1596617351" ]) # 实时读取输出 while True: output = process.read() if not output: if process.exit_code is not None: break print(output, end="", flush=True)
2. 利用Windows的conhost.exe转发输出
通过conhost.exe启动steamcmd,让它在控制台环境中运行,同时捕获输出:
import subprocess args = [ "conhost.exe", "steamcmd.exe", "+login", "anonymous", "+workshop_download_item", "281990", "899653076" ] # 设置bufsize=1启用行缓冲 process = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1 ) for line in iter(process.stdout.readline, ''): print(line, end='', flush=True) process.wait()
3. 使用stdbuf强制关闭缓冲(需类Unix环境,如Git Bash)
stdbuf可以强制修改程序的缓冲策略,适用于Windows下的Git Bash等类Unix环境:
import subprocess args = [ "stdbuf", "-i0", "-o0", "-e0", "steamcmd.exe", "+login", "anonymous", "+workshop_download_item", "281990", "899653076" ] process = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1 ) for line in iter(process.stdout.readline, ''): print(line, end='', flush=True) process.wait()
内容的提问来源于stack exchange,提问作者sh4dow

