Bash中find调用的子shell报错时如何让整体命令退出报错
问题根因
你对问题的判断存在偏差:find的-exec动作本身不会忽略子命令的退出码——只要子命令返回非0值,-exec就会返回假,最终find的退出码由最后执行的动作结果决定。默认配置下find不会因为单个文件的-exec执行失败就终止遍历,会继续处理剩余匹配到的文件,如果最后一个处理的文件刚好执行成功,整个find命令就会返回0,这才是你始终拿不到预期非0退出码的核心原因。
另外原命令存在两个隐患:
- 子shell中变量
$err未加双引号,当返回的错误信息包含空格时会触发test命令参数错误 - 直接将
{}拼接在sh -c的命令字符串中,一旦文件名包含空格、特殊符号甚至恶意字符串,会触发命令解析错误、命令注入风险
解决方案
方案1:GNU find 原生实现(适用于绝大多数Linux发行版)
利用find的短路逻辑和-quit动作,一旦检测到上传失败立刻终止遍历,同时返回非0退出码,同时修复原命令的隐患:
find . -name "*.zip" \ -print \ -exec sh -c ' err=$(curl -s --data-binary "@$1" http://mystorage.com | jq -r ".error") [ -z "$err" ] || [ "$err" = "file already exists" ] ' sh {} \; \ -o -quit
逻辑说明:
- 用
-print替换原有的-exec echo {} \;,实现同样的文件名打印效果,执行效率更高,如需和原命令行为完全一致,换回-exec echo {} \;也可正常运行 - 子shell通过位置参数
$1接收文件名,避免特殊字符解析问题,所有变量引用加双引号消除语法隐患 find按顺序求值表达式:子shell执行成功(上传成功/文件已存在,返回0即逻辑真)时,-o(或)后面的-quit不会触发,继续处理下一个文件;子shell执行失败(真正的上传错误,返回非0即逻辑假)时,立刻触发-quit终止整个find进程,命令整体返回非0退出码。
方案2:POSIX 兼容实现(适用于BSD find、macOS等无GNU扩展的环境)
用shell循环遍历find的结果,兼容性更强,逻辑更直观:
# 若使用bash,可先执行 shopt -s lastpipe 保证循环在当前shell执行,透传退出码 find . -name "*.zip" -print0 | while IFS= read -r -d '' file; do echo "$file" err=$(curl -s --data-binary "@$file" http://mystorage.com | jq -r ".error") if [ -n "$err" ] && [ "$err" != "file already exists" ]; then exit 1 fi done
逻辑说明:
- 用
-print0和read -d ''组合处理文件名,兼容包含任意特殊字符的文件名 - 一旦检测到符合终止条件的上传错误,直接在循环内执行
exit 1,整个脚本立刻终止并返回失败状态。
内容的提问来源于stack exchange,提问作者lnthai2002
相关产品推荐
相关产品推荐

