为何Shell脚本执行失败却返回成功退出状态?
我写了一段Shell脚本,用来在网络设备目录下对所有文件执行whisper工具,转录文件存在同一卷里。但有时网络异常(比如卷断开)会导致转录保存失败,典型错误如下:
Traceback (most recent call last):
File "/Users/db/Library/Python/3.11/bin/whisper", line 8, in
sys.exit(cli())
^^^^^
File "/Users/db/Library/Python/3.11/lib/python/site-packages/whisper/transcribe.py", line 413, in cli
os.makedirs(output_dir, exist_ok=True)
File "", line 225, in makedirs
FileNotFoundError: [Errno 2] No such file or directory: '.'
脚本相关代码片段:
whisper "$file" --model large >> log if [[ $? -eq 0 ]]; then echo "exit success: $?" echo "*****success****** $?, `uname -n`, $me" >> "${directory}/../${transdir}/${name}.txt" else echo "exit error: $?" echo "*****error****** $?, `uname -n`, $me" >> "${directory}/../${transdir}/${name}.txt" fi
正常执行成功会进入第一个if分支,但出现上述错误时,居然还是进入成功分支,仿佛whisper执行成功了一样。
补充说明:写入log文件没有问题。
更新1:测试错误参数的退出码
% whisper --bad_args; echo $? whisper: error: the following arguments are required: audio 2
更新2:whisper的入口脚本内容
#!/opt/local/Library/Frameworks/Python.framework/Versions/3.11/bin/python3.11 # -*- coding: utf-8 -*- import re import sys from whisper.transcribe import cli if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(cli())
核心原因
Python脚本抛出未捕获的异常时,默认会打印堆栈跟踪,但如果没有显式设置非零退出码,进程可能返回0(成功状态)。你遇到的FileNotFoundError属于运行时未捕获异常,whisper的cli()函数没有处理这个异常,导致Python解释器直接终止,但此时的退出码不一定是预期的非零值——在你的场景里,它返回了0,所以脚本误判为执行成功。
对比你测试的参数错误场景:whisper --bad_args返回码是2,因为这是argparse主动捕获的错误,argparse会显式调用sys.exit(code)设置非零退出码;但未捕获的运行时异常没有这个处理逻辑,退出码可能由系统决定,或者在某些环境下直接返回0。
解决方案
1. 基于输出内容判断执行结果
既然退出码不可靠,直接检查whisper的错误输出(堆栈跟踪打在stderr,需要先重定向到log):
# 把stderr也重定向到log,确保错误信息被记录 whisper "$file" --model large >> log 2>&1 # 检查log中是否包含错误关键词 if grep -q "Traceback\|FileNotFoundError" log; then echo "exit error: 运行时异常" echo "*****error****** 异常, `uname -n`, $me" >> "${directory}/../${transdir}/${name}.txt" elif [[ $? -eq 0 ]]; then echo "exit success: $?" echo "*****success****** $?, `uname -n`, $me" >> "${directory}/../${transdir}/${name}.txt" else echo "exit error: $?" echo "*****error****** $?, `uname -n`, $me" >> "${directory}/../${transdir}/${name}.txt" fi
2. 修改whisper的cli函数,强制异常返回非零码
如果能修改whisper的transcribe.py文件,给cli()函数加一层异常捕获,确保任何未处理的异常都返回非零退出码:
# 在transcribe.py的cli函数外层添加try-except def cli(): try: # 原cli函数的所有代码逻辑 ... except Exception as e: print(f"执行失败: {e}", file=sys.stderr) sys.exit(1)
这样所有未捕获的异常都会让进程返回1,Shell脚本里的$?就能正确识别错误。
3. 提前检查网络卷可用性
在执行whisper前,先检查目标目录是否可写,从源头避免后续出错:
# 检查转录目录是否存在且可写 target_dir="${directory}/../${transdir}" if [[ ! -w "$target_dir" ]]; then echo "*****error****** 目录不可写, `uname -n`, $me" >> "${target_dir}/${name}.txt" continue # 跳过当前文件,处理下一个 fi # 再执行whisper whisper "$file" --model large >> log 2>&1 ...
内容的提问来源于stack exchange,提问作者d-b

