14. 错误、信号和陷阱(一)

优质
小牛编辑
145浏览
2023-12-01

本节学习如何处理脚本运行中发生的错误。 好程序和坏程序之间的一个标志就是程序的 健壮性

退出状态

正如前面课程所说,好的程序退出时都会返回一个退出状态。脚本执行成功,则退出状态为 0,如果为非 0,则代表脚本执行失败。 检查你所调用的程序的退出状态是很重要的。同时,当你的脚本结束时返回一个有意义的状态码也是很重要的。例如以下代码,曾经在某个生产环境的 Linux 服务器上:

# Example of a really bad idea

cd $some_directory
rm *

为什么这个脚本不好呢?如果它没有错误,那么它是好的。脚本首先切换目录到 $some_directory,然后删除该目录下的文件。但是如果 $some_directory 变量中的目录不存在呢?此时,cd 命令执行失败,rm 会删除当前工作目录下的所有文件。这不是本意行为! 这里的问题是在执行 rm 命令之前,没有检查 cd 命令的退出状态。

检查退出状态

有几种检查退出状态的方法。一种是检查 $? 环境变量。$? 环境变量包含了上一个执行命令的退出状态。示例:

true; echo $?
false; echo $?

true 命令直接返回退出状态 0,false 命令直接返回退出状态 1。所以我们可以改造开头那个脚本如下:

# Check the exit status

cd $some_directory
if [ "$?" = "0" ]; then
 rm *
else
 echo "Cannot change directory!" 1>&2
 exit 1
fi

这个版本中,我们检查了 cd 命令的退出状态,如果不为 0 则打印错误信息,并以非 0 状态退出,若退出状态为 0 则执行 rm 命令。 我们可以使用 if 简化上述脚本,if 可以直接判断命令的退出状态,如下:

# A better way

if cd $some_directory; then
 rm *
else
 echo "Could not change directory! Aborting." 1>&2
 exit 1
fi

错误退出函数

我们可以提供一个退出函数来打印错误信息及退出,以减少代码的书写。

# An error exit function

error_exit()
{
 echo "$1" 1>&2
 exit 1
}

# Using error_exit

if cd $some_directory; then
 rm *
else
 error_exit "Cannot change directory! Aborting."
fi

AND 和 OR 命令列表

最后,我们可以使用 ANDOR 来简化我们的脚本。 AND 命令列表:

command1 && command2

command2 只会在 command1 退出状态为 0 时才会执行。 OR 命令列表:

command1 || command2

command2 只会在 command1 退出状态非 0 时才会执行。ANDOR 命令列表的退出状态为列表中最后一个执行命令的退出状态。 同样地,我们可以使用 truefalse 命令来验证:

true || echo "echo executed"
false || echo "echo executed"
true && echo "echo executed"
false && echo "echo executed"

利用这个技巧,我们可以用更简洁的方法重写开头的示例:

# Simplest of all

cd $some_directory || error_exit "Cannot change directory! Aborting"
rm *

而且如果发生了错误时不需要退出脚本,可以简写如下:

# Another way to do it if exiting is not desired

cd $some_directory && rm *

改进退出函数

我们可以将程序名称添加到错误信息中,以提示错误具体来自哪里。这在脚本复杂或脚本中引用了其他脚本时很有用。注意引入的 LINENO 环境变量,它可以帮你定位脚本发生错误的具体行数。

#!/bin/bash

# A slicker error handling routine

# I put a variable in my scripts named PROGNAME which
# holds the name of the program being run. You can get this
# value from the first item on the command line ($0).

PROGNAME=$(basename $0)

error_exit()
{

#    ----------------------------------------------------------------
#    Function for exit due to fatal program error
#    Accepts 1 argument:
#    string containing descriptive error message
#    ----------------------------------------------------------------


 echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
 exit 1
}

# Example call of the error_exit function. Note the inclusion
# of the LINENO environment variable. It contains the current
# line number.

echo "Example of error with line number and message"
error_exit "$LINENO: An error has occurred."

错误函数中使用的大括号用于 参数扩展。你可以在变量名两边加上大括号来跟其周边的字符分开(如:${PROGNAME})。有些人有在每个变量两边都加上大括号的习惯。第二个用法 ${1:-"Unknown Error"} 表示当第一个命令行参数未定义时,使用「Unknown Error」作为默认值代替。你可以在 BASH 的帮助文档中的 EXPANSIONS 一节找到更多关于参数扩展的信息。