前言
bash
set -e
set -o pipefail
这个世界很多人的狂妄源于他并不知道自己不知道,所以才有了那句话:知道的越多实际上不知道的越多,今天文章的开头便甩了两行代码,这很不常见,本来这两天在忙着ClickHouse的搭建,想着搞完总结一下,但是牵扯的东西太多,所以先拿出今天发现的这个亮点记录一下,原来脚本还可以这么写。
set -e 的作用
只要脚本中有任意命令返回非 0 状态(失败),脚本就会立即退出,可以看下面这个例子
bash
echo 1
cp not_exist_file /tmp/ # 假设这句失败
echo 2
- 有 set -e:脚本执行到 cp 就马上退出,不会执行 echo 2
- 没有 set -e:即使 cp 失败,脚本仍会继续执行 echo 2
它的意义就是防止错误被忽略,避免执行后续危险操作,特别适合以下场景
- 下载失败不能继续
- 解压失败不能继续
- 数据导入失败不能继续
如果不用 set -e,为了阻止错误后继续执行,就得向下面这样写了
bash
some_command
if [ $? -ne 0 ]; then
echo "some_command failed"
exit 1
fi
some_command || exit 1
some_command || { echo "failed"; exit 1; }
但是要注意,set -e 并不是对所有命令都触发,if 条件判断中的命令失败不会触发退出,比如下面这个命令执行错误
bash
if grep keyword file.txt; then
...
fi
使用 set -e 面临的问题
开启 set -e 后,脚本自动退出,那我要如何打印出"是什么命令失败了"、"失败的原因"呢?
实际上,set -e 自己不会打印任何错误信息,只会在某个命令失败时直接退出脚本
有些命名失败时自己会输出的信息,比如 cp file1 file2 失败通常会打印 cp: cannot stat 'file1': No such file or directory
而专业的做法可以使用 trap 捕获错误并打印日志,比如可以在脚本开头加上这样一句
bash
#!/bin/bash
set -e
trap 'echo "[ERROR] Command failed at line $LINENO: $BASH_COMMAND"' ERR
试验一下
编写一下测试脚本,执行
bash
#!/bin/bash
set -e
set -o pipefail
trap 'echo "[ERROR] Command failed at line $LINENO: $BASH_COMMAND"' ERR
rm not_exist_file
运行结果如下:
bash
$ ./testtrap.sh
rm: cannot remove 'not_exist_file': No such file or directory
[ERROR] Command failed at line 7: rm not_exist_file
除了rm命令本来的提示,trap给出了自定义提示,可以更加人性化
set -o pipefail 的作用
让管道命令的退出状态由"最右边命令"变成"整个管道中第一个失败的命令",使错误暴露出来不至于隐藏不见
示例代码如下
bash
cat file.txt | grep "keyword"
假设:
cat file.txt失败(文件不存在)grep依然成功(因为输入为空)
默认行为(不加 pipefail):
整个管道返回值是 grep 的,而 grep 是成功的 → 脚本认为整条命令成功
加上 pipefail 后:
整个命令返回 "cat file.txt 的失败",脚本能正确判断管道失败
结论
set -e的作用是只要脚本中有任意命令返回非 0 状态(失败),脚本就会立即退出,避免继续执行引发更大错误set -o pipefail的作用是让管道命令的退出状态由"最右边命令"变成"整个管道中第一个失败的命令",避免某些管道错误会被悄悄忽略- 有些方案不知道时根本就不知道自己不知道,知道了就发现居然还有这种解法
有些事就是那么巧,在我考虑将大量日志从MySQL迁移出时,评估了ClickHouse和Hive两个方向,最终选择了ClickHouse,之后突然发现合作商的SQL权限今天开了,一查他们也是ClickHouse,想到这里找人用同样的方法去查询了老东家的解决方案,居然是Hive,难道是英雄所见略同?哈哈~