子Shell以及返回值运行机制

这是一个非常经典且重要的 Shell 执行机制问题。直接给结论:

  1. 会新建子进程(子 Shell)吗?
    会。 使用 bash $PushAssets 会启动一个新的 Bash 进程来运行该脚本。
  2. set -e 能触发退出吗?
    能。 如果子脚本返回非 0 值,父脚本在 set -e 模式下会立即终止。

下面是详细的拆解和注意事项:

1. 进程模型

当你执行 bash $PushAssets 时:

  • 当前 Shell(父进程)会调用 forkexec 启动一个全新的 bash 进程。
  • 父进程会挂起(Wait),等待子进程结束。
  • 子进程执行完毕后,会将它的退出状态码(Exit Code)传回给父进程。

2. set -e 的工作原理

在父脚本中设置 set -e 后,Shell 会检查每一个命令的返回值。

  • 如果命令是 bash $PushAssets,父脚本拿到的返回值就是该子脚本中最后一条执行命令的返回值(或者是子脚本中显式调用的 exit X 的值)。
  • 只要这个返回值 != 0,父脚本就会认为这一行命令失败了,从而触发退出。

3. 这里的“陷阱”:子脚本内部是否设置了 set -e

这是最容易出错的地方。请看以下两个场景:

场景 A:子脚本内部没有 set -e

1
2
3
# sub.sh (子脚本)
ls /not_exist_folder # 这一行报错了,返回 2
echo "Finish" # 这一行成功了,返回 0

如果你在主脚本执行 bash sub.sh,子脚本最后一行 echo 成功了,所以整个子脚本的返回值是 0此时父脚本的 set -e 不会触发退出,因为它认为子脚本成功执行了。

场景 B:子脚本内部也写了 set -e(或者显式报错)

1
2
3
# sub.sh (子脚本)
set -e
ls /not_exist_folder # 这里直接报错,子脚本立即退出并返回 2

此时执行 bash sub.sh,返回值是 2父脚本的 set -e 会立即感知并触发退出。


4. 最佳实践建议

为了确保链路上的错误能百分之百传递给最外层,建议:

  1. **所有的脚本头部都加上 set -e**。
  2. 如果是为了环境变量或共享上下文,考虑使用 source 而不是 bash
    • source $PushAssets (或 . $PushAssets):在当前进程中运行。
    • 如果子脚本出错(且子脚本有 set -e),它会直接结束当前进程。
  3. 如果使用了管道(Pipe)
    如果在调用处写了 bash $PushAssets | tee log.txt,默认情况下即便子脚本崩溃,只要 tee 成功了,父脚本就不会退出。此时需要加上:
    1
    2
    set -e
    set -o pipefail # 确保管道中任何一环出错都算整体出错

总结

你的写法 bash $PushAssets 配合 set -e 是有效的,但前提是 $PushAssets 那个脚本本身要能返回非零状态码(最简单的办法就是在子脚本里也加上 set -e)。