shell 脚本的错误清理

Mir*_*ral 6 bash dash shell-script error-handling

为 shell 脚本包含错误清除逻辑的最佳方法是什么?

具体来说,我有一个脚本可以执行以下操作:

mount a x
mount b y
setup_thing
mount c z
do_something
umount z
cleanup_thing
umount y
umount x
Run Code Online (Sandbox Code Playgroud)

任何安装座及其do_something本身都可能会失败。例如,如果mount c z失败,我希望脚本在退出之前卸载已成功安装的安装。

我不想多次重复清理代码,也不想将所有内容包装到 if-nest 中(因为如果添加额外的安装,则必须重新缩进所有内容)。

有没有办法创建一个finally-stack之类的东西,所以上面的内容可以写成:

set -e
mount a x && finally umount x
mount b y && finally umount y
setup_thing && finally cleanup_thing
mount c z && finally umount z
do_something
Run Code Online (Sandbox Code Playgroud)

这个想法是,一旦“finally”命令被注册,它将在退出、通过或失败时执行(以相反的顺序)该命令——但是没有成功设置的东西不会被清理(因为如果安装失败,清理可能无法安全执行)。

或者换句话说,如果一切成功,那么它将按照 、 、 、 的顺序执行umount z——cleanup_thing如果umount yumount x失败则相同do_something。但如果mount b y失败则只会执行umount x.

(无论它需要以 0 或非 0 适当退出,尽管我不需要在失败时保留确切的退出代码。)

我知道有一个trap内置命令可以让您在退出时运行命令,但它只支持一个命令并每次都会替换它。有没有办法将其扩展到像上面这样的堆栈中?或者其他一些干净的方法来做到这一点?

在 Ye Olde C 代码的错误处理模式中,您可能会实现类似的效果,x || goto cleanup_before_x但当然没有 goto。

理想情况下,它可以在 (da)sh 中工作,尽管如果这可以简化事情,我也可以要求 bash 。(也许使用数组?)

agc*_*agc 0

修改为单独处理预先存在的挂载点。假设代码采用这种格式,其中可能导致问题的行每行有一个命令,即mountumount

mount a x
mount b y
setup_thing
mount c z
do_something
umount z
cleanup_thing
umount y
umount x
Run Code Online (Sandbox Code Playgroud)

这个拼凑可能会起作用...将此代码复制到脚本的第二行:

mount | cut -d' ' -f3 | sed 's/.*/^u\?mount [^[:space:]]\* &$/' | 
  grep -v -f - $0 | sed 2d | exec sh -s -- "$@"
Run Code Online (Sandbox Code Playgroud)

它(理论上)是如何工作的:

  1. 使用mountcut来创建预先存在的挂载点的列表,不要管它。
  2. 用于创建与使用任何这些安装点的模式或命令相匹配的模式sed列表。grepmountumount
  3. 用于grep -v搜索当前脚本中与先前模式列表grep匹配的任何行。留下所有适合运行的代码。
  4. 用于sed删除第二行,以防止递归。
  5. 用于exec sh -s -- "$@"仅运行该代码以及任何命令行参数。超过该exec线的任何内容都不会运行。

exec通过更改cat #并检查输出来预先测试。如果看起来不错,就放exec回去。