如何从Python运行Bash脚本并获取完整输出及正确返回码?
解决Bash脚本调用时的输出丢失与返回码不正确问题
我来帮你一步步解决这两个问题,先理清楚根源,再给出具体的修改方案:
一、返回码始终为0的根源与修复
你的Bash脚本返回码始终为0,核心原因是脚本最后一个命令是echo "End test"——不管前面的子进程是否失败,这个echo命令都会成功执行,返回码为0,导致整个脚本的退出码被覆盖为0。另外,原脚本中的wait $pids只会返回最后一个子进程的退出状态,无法捕获前面子进程的失败。
修改后的Bash脚本(testbash.sh)
我们需要跟踪所有子进程的退出状态,确保任何一个子进程失败时,脚本最终返回非0码:
export MAX_SEED=2 echo "Start test" pids="" # 初始化全局退出码为0 global_exit_code=0 for seed in `seq 1 ${MAX_SEED}` do python -c "raise ValueError('test')" & pids="${pids} $!" done echo "pids: ${pids}" # 逐个等待子进程,记录失败状态 for pid in $pids; do wait $pid # 如果当前子进程失败,更新全局退出码 if [ $? -ne 0 ]; then global_exit_code=$? fi done # 如果有子进程失败,直接退出;否则继续打印结束信息 if [ $global_exit_code -ne 0 ]; then exit $global_exit_code fi echo "End test"
二、输出丢失的根源与修复
你的Python代码中存在一个明显的错误:在except Empty:分支里尝试print(line),但此时line变量并未被定义,会触发NameError,导致读取输出的线程意外终止,进而丢失部分内容。此外,线程结束后队列中可能还有未读取的剩余输出,需要额外处理。
修改后的Python调用代码
from __future__ import print_function import sys from subprocess import PIPE, Popen, STDOUT from threading import Thread try: from queue import Queue, Empty except ImportError: from Queue import Queue, Empty # 兼容Python 2.x ON_POSIX = 'posix' in sys.builtin_module_names def enqueue_output(out, queue): for line in iter(out.readline, b''): queue.put(line.decode('ascii')) out.close() # 推荐方式:给脚本添加执行权限后,直接调用(无需shell=True,更安全) # p = Popen(['./testbash.sh'], stdout=PIPE, stderr=STDOUT, bufsize=1, close_fds=ON_POSIX) # 如果必须使用source执行(比如依赖当前shell环境),保留shell=True p = Popen(['. testbash.sh'], stdout=PIPE, stderr=STDOUT, bufsize=1, close_fds=ON_POSIX, shell=True) q = Queue() t = Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True # 随主进程退出 t.start() # 读取队列直到线程结束 while t.is_alive(): try: line = q.get(timeout=0.1) print(line, end='') except Empty: continue # 无输出时跳过,避免未定义变量错误 # 确保队列中剩余的输出全部被读取 while not q.empty(): line = q.get() print(line, end='') # 获取最终返回码 return_code = p.wait() print('returncode = {}'.format(return_code))
额外优化建议
- 给
testbash.sh添加执行权限(chmod +x testbash.sh),然后Python中直接用['./testbash.sh']调用,去掉shell=True,这样更安全,避免shell注入风险。 - 如果不需要实时输出,也可以简化Python代码,直接用
communicate()一次性获取所有输出,这样无需线程和队列:
这种方式输出不会丢失,返回码也正确,唯一的缺点是需要等待脚本执行完才能看到输出。p = Popen(['./testbash.sh'], stdout=PIPE, stderr=STDOUT, close_fds=ON_POSIX) output, _ = p.communicate() print(output.decode('ascii')) print('returncode = {}'.format(p.returncode))
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

