为何Python subprocess的stdout会混入无效数据?
问题描述
我正在开发一款Python应用,用于在AWS远程Docker容器中执行命令,并将输出保存至文件。远程执行的命令会生成二进制数据(数据库转储)。
若启动下载后不进行任何操作,应用运行正常;但如果在下载过程中按下回车键,或在终端窗口滚动鼠标滚轮,输出文件中就会出现^M或奇怪字符。
示例代码
#!/usr/bin/env python3 import npyscreen import curses import subprocess MY_REGION=... MY_CLUSTER=... MY_TASK=... MY_CONTAINER=... class ProgressForm(npyscreen.Popup): def create(self): self.progress = self.add( npyscreen.TitleSliderPercent, step=1, out_of=100, name="Progress" ) def activate(self): cmd = subprocess.Popen( [ "aws", "--region", MY_REGION, "ecs", "execute-command", "--cluster", MY_CLUSTER, "--task", MY_TASK, "--container", MY_CONTAINER, "--command", "python -c 'for i in range(500_000): print(i)'", "--interactive", ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0, ) total_size = 3889129 downloaded = 0 with open("out.log", "wb") as f: while True: chunk = cmd.stdout.read(1024) if not chunk: break f.write(chunk) downloaded += len(chunk) self.progress.set_value(min(downloaded/total_size*100, 100)) self.progress.display() self.parentApp.switchForm(None) class MAIN(npyscreen.FormBaseNew): def create(self): self.items = self.add( npyscreen.GridColTitles, col_titles=["Column"], select_whole_line=True, ) self.items.add_handlers({curses.ascii.NL: self.item_chosen}) def activate(self): for i in range(4): self.items.values = [ ["Row Data"] ] self.edit() def item_chosen(self, inpt): self.parentApp.switchForm("progressForm") class App(npyscreen.NPSAppManaged): def onStart(self): self.addForm("MAIN", MAIN, name="My App") self.addForm("progressForm", ProgressForm) if __name__ == "__main__": app = App().run()
异常输出示例
按下回车键时的异常:
... 10667 10668 10669 ...
滚动鼠标滚轮时的异常:
... 17451 17452 17453 ^[[<65;121;31M17454 17455 17456 17457 ...
原因及解决方法
原因
问题根源在于aws ecs execute-command的--interactive参数。这个参数会让AWS建立交互式终端会话,此时本地终端的所有输入(包括回车键、鼠标滚动触发的终端控制序列)都会被转发到远程容器的会话中。远程执行环境会将这些输入当作终端交互内容处理,甚至直接回显出来,最终混在命令的正常输出里,被你的subprocess读取并写入文件。
解决方法
方案1:移除交互式参数(优先推荐)
如果你的命令不需要用户交互(比如数据库转储这类无输入需求的操作),直接删除--interactive参数即可。此时AWS会建立非交互式执行会话,本地终端的输入不会被转发到远程,也就不会混入输出文件。
修改后的命令部分代码:
cmd = subprocess.Popen( [ "aws", "--region", MY_REGION, "ecs", "execute-command", "--cluster", MY_CLUSTER, "--task", MY_TASK, "--container", MY_CONTAINER, "--command", "python -c 'for i in range(500_000): print(i)'", # 移除--interactive参数 ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0, )
方案2:过滤终端控制字符(仅当必须保留交互时使用)
如果确实需要保留交互式功能,需要在读取stdout时手动过滤终端控制序列和输入回显内容。可以借助pty模块管理终端会话,或者编写正则表达式匹配并移除ANSI控制字符,但这种方式复杂度较高,仅推荐在必要时使用。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

