Python 2中Subprocess输出到文件时如何捕获stdout和stderr到变量
问题与解决方案
原代码
# logging output and errors to file instead of PIPE # as when the output is over 64K the process can hang for a long time stdout_file = os.path.join("/var/log/", "migrate_stdout.log") stderr_file = os.path.join("/var/log/", "migrate_stderr.log") # append to the file to prevent blowing it away every upgrade stdout_handle = open(stdout_file, "a") stderr_handle = open(stderr_file, "a") migratecmd = "exec /opt/migrate.sh %s %s" % ( body['stadium'], body['version'] ) migrateproc = subprocess.Popen(migratecmd, shell=True, stdout=stdout_handle, stderr=stderr_handle, preexec_fn=self.demote()) result = migrateproc.wait() if result != 0: # stdout,stderr = migrateproc.communicate()
问题描述
现有代码将子进程的输出和错误信息写入日志文件而非管道,以避免输出超过64K时进程长时间挂起。请问是否存在无需读取文件的方法,能同时将stdout和stderr捕获并赋值给变量?官方文档指出.communicate()方法将始终返回None,希望找到可行的替代方案。
解决方案
可以通过线程实时读取子进程输出流的方式,同时完成日志写入和内存缓存,既避免管道缓冲区满导致的进程挂起,又能直接拿到输出内容赋值给变量。具体实现如下:
代码示例
import os import subprocess import threading import io def stream_processor(stream, log_file, content_buffer): # 逐行读取流内容,同时写入日志文件和内存缓冲区 for line in iter(stream.readline, b''): log_file.write(line) content_buffer.write(line) stream.close() # 定义日志文件路径 stdout_log_path = os.path.join("/var/log/", "migrate_stdout.log") stderr_log_path = os.path.join("/var/log/", "migrate_stderr.log") # 以追加模式打开日志文件,同时准备内存缓冲区 with open(stdout_log_path, "ab") as stdout_log, open(stderr_log_path, "ab") as stderr_log: stdout_buf = io.BytesIO() stderr_buf = io.BytesIO() # 构造迁移命令 migratecmd = "exec /opt/migrate.sh %s %s" % (body['stadium'], body['version']) # 使用PIPE获取子进程的输出流 migrateproc = subprocess.Popen( migratecmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=self.demote() ) # 启动线程分别处理stdout和stderr stdout_thread = threading.Thread(target=stream_processor, args=(migrateproc.stdout, stdout_log, stdout_buf)) stderr_thread = threading.Thread(target=stream_processor, args=(migrateproc.stderr, stderr_log, stderr_buf)) stdout_thread.start() stderr_thread.start() # 等待子进程执行完成,以及线程处理结束 exit_code = migrateproc.wait() stdout_thread.join() stderr_thread.join() # 将缓冲区内容转为字符串(根据实际编码调整) stdout_content = stdout_buf.getvalue().decode('utf-8') stderr_content = stderr_buf.getvalue().decode('utf-8') # 子进程执行失败时直接使用缓存的输出内容 if exit_code != 0: print(f"迁移失败,错误信息:{stderr_content}") # 这里可以根据需求处理stdout_content和stderr_content
方案说明
- 避免管道阻塞:通过线程实时读取
subprocess.PIPE的输出流,不会让子进程因为管道缓冲区满而挂起,解决了原代码中用管道的痛点。 - 双重输出处理:线程同时将内容写入日志文件和内存缓冲区(
io.BytesIO),既保留了日志持久化的需求,又能直接从内存中获取输出内容赋值给变量。 - 原代码中communicate返回None的原因:原代码将
stdout/stderr直接指向了文件句柄,而非subprocess.PIPE,因此communicate()没有可读取的管道数据,自然返回None。本方案改用PIPE后,配合线程读取即可正常获取内容。
内容的提问来源于stack exchange,提问作者crmpicco
相关产品推荐
相关产品推荐

