我们之前说过,linux 每个命令都使用退出状态码(exit status) 来告诉shell 该命令已经完成。每次命令执行后返回的退出状态码都会用$?
变量保存。
另外,我们也可以在脚本的末尾指定exit xxx,设定xxx 使得该脚本返回指定的退出码。但退出码的范围为0-255,因此如果退出码为大于该区间的数,则shell 会通过模运算取余。如300,则会返回44,300%256=44。
在linux 中,有如下的退出码:
0 命令成功结束
1 一般性未知错误
2 不适合的shell 命令
126 命令不可执行
127 为没找到命令
128 无效的退出参数
128+x 与linux 信号x 相关的严重错误
130 通过ctrl+c 终止的命令
255 正常范围外的退出码
但是,我们编写时即使捕获到了异常输出,当然脚本运行错误也会有对应的异常报错,我们却难以一一捕获他们。
以及,更可怕的情况是,我们的命令成功运行了,但是却达到了意外的目的。
比如:
cd $dir_name && rm *
但当$dir_name
变量不存在时,cd 会默认进入家目录:
pwd
/home/yzpeng/test
cd $no
$ pwd
/home/yzpeng
那么,我们将会删掉家目录下的全部文件!
因此,在真正运行命令前,使用前打印一下将是非常有用的。
重要命令,使用前打印一下很有用。
双引号的echo 是会打印变量的:
$ echo "cd $dir_name && rm *"
cd && rm *
以及通过条件语句进行判断。比如:
dir_name=/path/not/exist
if [ -d $dir_name ]
then cd $dir_name
else
echo "$dir_name is not existing."
fi
/path/not/exist is not existing.
我们在使用脚本时,可以使用bash -x
参数,其可以在执行每一行命令之前,打印该命令。一旦出错,这样就比较容易追查。
$ cat > script.sh
a=3
b=4
c
echo 'good'
$ bash script.sh
script.sh: line 3: c: command not found
good
$ bash -x script.sh
+ a=3
+ b=4
+ c
script.sh: line 3: c: command not found
+ echo good
good
我们很轻松的找到,问题出在c
上。
输出的命令之前的+号,是由系统变量PS4决定,可以修改这个变量。
export PS4='$LINENO + '
$ bash -x script.sh
1 + a=3
2 + b=4
3 + c
script.sh: line 3: c: command not found
4 + echo good
good
上面的例子不难发现,即使脚本发生错误,shell 也并不会立即终止:
export PS4='$LINENO + '
$ bash -x script.sh
1 + a=3
2 + b=4
3 + c
script.sh: line 3: c: command not found
4 + echo good
good
这种行为很不利于脚本安全和除错。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。
我们可以直接在命令中使用逻辑运算符解决:
command || { echo "command failed"; exit 1; }
当命令的返回值非0,则脚本会立刻结束。
或者:
command1 && command2
只有第一个命令成功,第二个命令才会继续运行。
但这样书写较为麻烦。
我们同样可以利用bash 的参数-e
,它使得脚本只要发生错误,就终止执行:
$ bash -xe script.sh
1 + a=3
2 + b=4
3 + c
script.sh: line 3: c: command not found
如果我们希望在脚本中内置这种找错或终止的语句,可以使用set 命令。
$ cat script.sh
set -e
set -x
a=3
b=4
c
echo 'good'
$ bash script.sh
3 + a=3
4 + b=4
5 + c
script.sh: line 5: c: command not found
我们还可以让set -e
在指定位置打开或关闭。
比如:
set -e
command 1
set +e
command1
command2
set -e
就可以让1,2 位置的命令可以在运行失败的情况下,使脚本继续运行。
set -e 有个例外,就是不适用于管道命令。
对于管道符号组成的命令,shell 只会把最后一个子命令的返回值最为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因而set -e 也就失效了:
❯ cat > script.sh
a=3
b | echo $a
c=33
echo $c
^C
❯ bash -e script.sh
3
script.sh: line 2: b: command not found
33
对此,我们可以设置-o pipefail
用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行:
❯ zsh -o pipefail -e script.sh
3
script.sh:2: command not found: b
此外,包括 -e, -x 等简写,也都有自己对应的全称:
set -o errexit # -e
set -o nounset # -u
set -o pipefail
执行脚本的时候,如果遇到不存在的变量,shell 默认会忽略它:
❯ cat > script.sh
a=3
cd $b
echo $a
^C
❯ zsh -u script.sh
script.sh:2: b: parameter not set
❯ zsh script.sh
3
通过设置-u 参数,遇到不存在的变量就会报错,并停止执行。
对于严格的脚本,我们可以在开头就声明 -e, -u, -o pipefail 等选项。
[1]
Bash 脚本中的错误处理 | 《Linux就该这么学》 (linuxprobe.com): https://www.linuxprobe.com/bash-scripts-error-handle.html
[2]
脚本除错 - Bash 脚本教程 - 网道 (wangdoc.com): https://wangdoc.com/bash/debug.html