如何捕获Python Popen子进程启动的孙进程输出?
问题:捕获子进程重启后孙进程的输出
原工作代码
以下代码可正常处理subprocess.Popen启动的子进程STDOUT:
print("launching child") child=subprocess.Popen(params,stdout=subprocess.PIPE) for line in child.stdout: #do processing print("processed:"+line.decode()) print("child dead :(")
问题现象
当子进程因UAC或其他原因自行重启时,原循环会退出,孙进程的STDOUT会直接输出到控制台,示例如下:
(venv) > .\parent.py launching child Processed:woot I started Processed:doing stuff ... Processed:shutting down for restart child dead :( (venv) > woot I started doing stuff
解决办法
方案1:通过Windows作业对象追踪进程树
Windows作业对象可将进程及其所有子进程(包括重启后的孙进程)纳入同一组,持续监控整个进程树的输出。步骤如下:
- 用
pywin32库创建作业对象 - 将启动的子进程关联到作业对象
- 监控作业内所有进程的输出,子进程重启后,新的孙进程仍属于该作业对象,输出可被捕获
示例代码:
import subprocess import win32job import win32api import win32con # 创建作业对象 job = win32job.CreateJobObject(None, "") # 配置作业限制,确保子进程重启后仍归属该作业 info = win32job.QueryInformationJobObject(job, win32job.JobObjectExtendedLimitInformation) info['BasicLimitInformation']['LimitFlags'] = win32job.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | win32job.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK win32job.SetInformationJobObject(job, win32job.JobObjectExtendedLimitInformation, info) # 启动子进程并关联到作业 params = ["your_child_process.exe"] child = subprocess.Popen(params, stdout=subprocess.PIPE, creationflags=win32con.CREATE_NEW_PROCESS_GROUP) win32job.AssignProcessToJobObject(job, child._handle) # 持续读取输出,即使子进程重启也能捕获孙进程内容 try: while True: line = child.stdout.readline() if not line: # 检查作业内是否还有存活进程 processes = win32job.QueryInformationJobObject(job, win32job.JobObjectBasicProcessIdList) if not processes['ProcessIdList']: break continue print("processed:" + line.decode()) finally: win32job.TerminateJobObject(job, 0) child.stdout.close()
方案2:通过命名管道重定向输出
在Windows上创建命名管道,让子进程及其重启后的孙进程都将STDOUT写入该管道,父进程持续读取管道内容:
- 父进程创建命名管道
- 启动子进程时将其STDOUT重定向到命名管道
- 确保子进程重启时,新进程依然使用同一命名管道输出
- 父进程持续监听管道,读取所有写入内容
示例代码:
import subprocess import win32pipe import win32file import pywintypes # 创建命名管道 pipe_name = r'\\.\pipe\ChildOutputPipe' pipe = win32pipe.CreateNamedPipe( pipe_name, win32pipe.PIPE_ACCESS_INBOUND, win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT, 1, 65536, 65536, 0, None ) # 等待子进程连接管道 win32pipe.ConnectNamedPipe(pipe, None) # 启动子进程,将STDOUT重定向到管道 params = ["your_child_process.exe"] startup_info = subprocess.STARTUPINFO() startup_info.dwFlags |= subprocess.STARTF_USESTDHANDLES startup_info.hStdOutput = pipe child = subprocess.Popen(params, startupinfo=startup_info, creationflags=subprocess.CREATE_NO_WINDOW) # 持续读取管道内容,断开后重新等待连接 try: while True: try: data = win32file.ReadFile(pipe, 65536) if not data[1]: win32pipe.DisconnectNamedPipe(pipe) win32pipe.ConnectNamedPipe(pipe, None) continue print("processed:" + data[1].decode()) except pywintypes.error as e: if e.winerror == 109: # 管道已关闭 win32pipe.DisconnectNamedPipe(pipe) win32pipe.ConnectNamedPipe(pipe, None) else: raise finally: win32file.CloseHandle(pipe) child.terminate()
注意事项
- 方案1依赖
pywin32库,需先安装:pip install pywin32 - 方案2需确保子进程重启时,启动命令仍指定输出到同一命名管道
- 两种方案均需管理员权限,避免UAC限制导致操作失败
内容的提问来源于stack exchange,提问作者Eph
相关产品推荐
相关产品推荐

