你好,世界!
命令行解释器 shell 是 UNIX 系统一个非常重要的理念和工具。GNU's Not UNIX,所以大多数 GNU 系统都带有至少一个 shell 命令行解释器,其中最著名的是 GNU BASH,简称 BASH 或 bash。
bash 是如此流行,如此好用,所以无论是资深黑客和系统管理员,还是普通用户和编程新手,都会经常打开命令行输入一段 bash 代码来执行一些任务。他们有时也会编写 bash 脚本用来更灵活地执行各种经常操作的任务。然而,由于 bash 对运行错误的处理并不那么直接明了,它也时常为使用者带来调试困难。其实,如果有良好的编程风格和统一的错误处理,我们还是能够提早发现很多 bash 脚本中的错误并及时中断执行中的脚本。
自由软件基金会的技术团队有一个关于 bash 的 编程风格建议,里面针对没有函数定义的 bash 脚本给出了一个错误处理的建议代码。我们就来详细分析一下这段简短的错误处理代码:
if ! test "$BASH_VERSION"; then echo "error: shell is not bash" >&2; exit 1; fi
shopt -s inherit_errexit 2>/dev/null ||: # ignore fail in bash < 4.4
set -eE -o pipefail
trap 'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2' ERR
使用说明:
这段代码的使用非常简单。如果你有一个 bash 脚本,并且脚本里没有函数定义,那么你可以直接把上述代码复制到你的脚本最前面。如果你的脚本运行时出现错误,那么脚本会在错误发生时中止,并打印出相关的错误信息以供调试分析。
如果你是一个 bash 初学者,虽然可以使用这段代码,但是也希望能够对代码有比较好的理解。那么我们就一起来看一下。
从 if 开始到 fi 结束是一个完整的 bash 的条件语句,其格式如下:
if test-commands; then
consequent-commands;
[elif more-test-commands; then
more-consequents;]
[else alternate-consequents;]
fi
if 按顺执行判断条件。如果一个 test-commands; 的值为零(表示逻辑真),则相应的 consequent-commands; 会执行,并结束条件语句。
! 表示对表达式结果取反,其格式如下:
! expression
如果表达式 expression 的结果是零,那么上述表达式的结果就是非零(表示逻辑假)。
test 是 bash 的一个内置函数,其格式如下:
test expression
test 根据 expression 的结果返回零或非零。如果 expression 是一个长度为零的字符串,那么 test 返回值为非零(假)。
这是 bash 设置的一个变量,它返回当前 bash 的版本号,通常是一个字符串。如果该变量没有设置,那么这是一个长度为零的空字符串。
echo 是 bash 的一个内置函数,其格式如下:
echo string
echo 执行的结果就是在终端输出字符串 string。
">&" 是重定向操作符,">&2" 把前一个命令(此处是 echo)的标准输出(1)和标准错误输出(2)都定向到标准错误输出(2),即都默认输出到终端。
exit 是 bash 的一个内置函数,其格式如下:
exit [number]
number 是 exit 的返回值。如果 number 是零,表示执行成功;如果是非零,则执行失败。
综合上述,这个 if 语句的意思就是:如果环境变量 $BASH_VERSION 未定义(为空),即当前命令行解释器不是 bash,则在终端输出错误信息:
error: shell is not bash
然后,退出脚本,返回值为1(失败)。
shopt 是 bash 的又一个内置函数,其格式如下:
shopt [-pqsu] [-o] [optname …]
其后续参数 "-s inherit_errexit" 设置确保子命令行进程会继承父命令行进程的 errexit 选项,即子命令行对
exit [number]
的 number 为非零值时的处理方式和父命令行一致。
这是把 shopt 命令的标准错误输出(2)丢弃(/dev/null 相当于垃圾桶),不在终端显示。
是命令行的逻辑或,其格式如下:
command1 || command2
表示只有在 command1 返回非零值时,command2 才执行。
":" 也是 bash 的内置命令,它实际什么都不做,它的返回值为零。
"#" 后面都是 bash 的注释。
set 是 bash 的又一个内置函数,它可以用来设置 bash 的各种可选项。"-eE -o pipefail" 是常见的把 bash 设置为一旦出现错误(比如某个命令返回非零)就停止执行下面的命令。这能保护脚本不在错误发生后继续执行。
trap 'echo "$0:$LINENO:error: "$BASH_COMMAND" returned $?" >&2' ERR
trap 是 bash 的一个内置函数,它的格式如下:
trap [-lp] [arg] [sigspec …]
第四行中的 arg 是:
'echo "$0:$LINENO:error: \"$BASH_COMMAND\" returned $?" >&2'
而 sigspec 是 ERR。它们放在一起的意思是一旦脚本中有命令、管道等返回非零值,那么 arg 就会执行。我们来看看 arg 究竟做些什么。
首先,两个单引号之间的是一条完整的 bash 命令,它使用 echo 把双引号之间的命令输出到标准错误通道(2),即终端显示。
其次,$0 会翻译成 bash 执行时出错的命令或脚本名称。$LINENO 会翻译成出错时的源代码文件的行号。
再次," 表示不解释双引号。$BASH_COMMAND 翻译成出错正在执行的命令名称。
最后,$? 会翻译成最后命令或管道的返回值,即错误状态。
所以,整个第四行的输出结果就是
脚本名称:行号:error: 命令名称 return 数字
把这段 bash 代码放在你的脚本前面就会起到脚本执行发生返回状态错误时,脚本会中止执行,并且你会在终端看到比较具体的错误信息。建议你在你每个 bash 脚本里都加上这一段。
最后,需要指出 GNU BASH 是自由软件,你可以下载它的源代码来学习、修改,并分享你的心得。
如果你对 GNU BASH 还有问题,立伯乐或许可以帮你。
用 GNU BASH 会让你在开发中游刃有余、灵活自由!