Skip to content

编写高可靠性脚本

参考腾讯文章:https://mp.weixin.qq.com/s/VmM_U4RefRBHwIw8NegC8Q

1. 提高可靠性

set -x -e -u -o pipefail
  • -x:在执行每一个命令之前把经过变量展开之后的命令打印出来

  • -e:遇到一个命令失败(返回码非零)时,立即退出

    但对管道中的命令不起作用。若确实想要忽略某个命令的成败,可以使用逻辑操作符:

some_cmd || true        # 即使some_cmd失败了,仍然会继续运行some_cmd || RET=$?      # 或者可以这样来收集some_cmd的返回码,供后面的逻辑判断使用
  • -u:试图使用未定义的变量,就立即退出。

    但有时候在已经设置了-u 后,某些地方还是希望能把未定义变量展开为空串,可以这样写:

${SOME_VAR:-}#  bash变量展开语法
  • -o pipefail :只要管道中的一个子命令失败,整个管道命令就失败。

    pipefail 与 -e 结合使用的话,就可以做到管道中的一个子命令失败,就退出脚本。

2. 防止重复运行

在一些场景中,我们通常不希望一个脚本有多个实例在同时运行。比如用 crontab 周期性运行脚本。

flock 通过文件锁的方式来保证独占运行,并且还有一个好处是进程退出时,文件锁也会自动释放,不需要额外处理。

flock [options] <file|directory> <command> [command args]

OPTIONS
    -s, --shared                    获取共享锁
    -x, -e, --exclusive             获取排他锁,默认选项
    -n, --nb, --nonblock            若不能立即获取锁则会失败而不是等待
    -w, --wait, --timeout seconds   在seconds内没有获取到则失败
    -E, --conflict-exit-code number 失败时的退出码

注意,该命令锁定的是<file|directory>,而不是<command>

file不存在,则会自动创建该文件。

# 第一个终端
[root@dev ~]# flock -e file.lock read -p '> '
> 
# 在另一个终端再次获取文件锁,会一直等待
[root@dev ~]# flock -e file.lock read -p '> '

也可以在原有脚本里使用 flock。可以把文件打开为一个文件描述符,然后使用 flock 对它上锁(flock 可以接受文件描述符参数):

exec 123<>lock_myscript   # 把lock_myscript打开为文件描述符123
flock  --wait 5  123 || { echo 'cannot get lock, exit'; exit 1; }

3. 意外退出时杀掉所有子进程

脚本通常会启动好多子脚本和子进程,当父脚本意外退出时,子进程其实并不会退出,而是继续运行着。

这种情况可以利用 trap 命令在脚本退出时 kill 掉它整个进程组($$):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT

不过如果父进程是用 SIGKILL (kill -9) 杀掉的,就不行了。因为 SIGKILL 时,进程是没有机会运行任何代码的。