如何在Python中捕获Shell脚本的输出与错误信息?
问题描述
我有一个可向控制台输出内容的简单Shell脚本,希望在Python中通过subprocess.run()等方法将脚本的标准输出和错误信息捕获到变量中,实际场景是对成百上千个proto文件执行protoc编译。目前调用脚本时它能正常运行并向控制台输出内容,但我无法将这些输出获取到Python中,尝试过以下几种写法均无效:
测试脚本test.sh:
# test.sh echo $1 $2
尝试的Python代码:
proc = subprocess.run(["test.sh", "test", "test2"], encoding="utf-8", shell=True, text=True, capture_output=True) print(proc.stdout)
session = subprocess.Popen(['test.sh', "test", "test2"],stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) stdout, stderr = session.communicate() print(stdout)
with subprocess.Popen(["test.sh", "test", "test2"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) as p: for line in p.stdout: print(line)
问题原因
核心问题是**shell=True的传参方式错误**:
当设置shell=True时,subprocess会启动系统默认的shell(比如bash),此时命令需要以单个字符串的形式传入,而不是列表。如果用列表传参,shell会把列表的第一个元素作为要执行的命令,后续的元素不会被当作脚本的参数,反而会被当作shell本身的参数,导致你的test.sh根本没收到test和test2这两个参数,甚至可能脚本执行逻辑不符合预期,最终捕获不到输出。
解决方法
方法1:修正shell=True的传参方式
把命令整合成单个字符串,参数直接拼接在后面:
# 使用subprocess.run proc = subprocess.run("test.sh test test2", encoding="utf-8", shell=True, text=True, capture_output=True) print(proc.stdout) # 会输出 "test test2" # 使用subprocess.Popen session = subprocess.Popen("test.sh test test2", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True) stdout, stderr = session.communicate() print(stdout)
方法2:不使用shell=True(推荐,更安全)
shell=True存在注入风险,尤其是当参数来自外部输入时。更安全的方式是去掉shell=True,直接用列表传参,但需要确保test.sh有可执行权限,或者明确指定shell解释器来运行脚本:
方式A:给脚本添加可执行权限
先给test.sh添加执行权限:
chmod +x test.sh
然后Python代码:
proc = subprocess.run(["./test.sh", "test", "test2"], encoding="utf-8", text=True, capture_output=True) print(proc.stdout)
方式B:明确指定bash解释器运行脚本
如果不想修改脚本权限,可以直接调用bash来执行脚本:
proc = subprocess.run(["bash", "test.sh", "test", "test2"], encoding="utf-8", text=True, capture_output=True) print(proc.stdout)
额外注意事项
- 如果你的实际场景中需要处理大量protoc编译的输出,建议使用
text=True(或encoding)确保输出是字符串而非字节流,方便后续处理。 - 若需要实时捕获输出(逐行处理),可以使用
Popen的stdout迭代,但要注意设置bufsize=1和text=True来实现行缓冲:
with subprocess.Popen(["./test.sh", "test", "test2"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1) as p: for line in p.stdout: print(f"捕获到输出:{line.strip()}")
内容的提问来源于stack exchange,提问作者darthbane426
相关产品推荐
相关产品推荐

