一、问题背景

在写Shell脚本的时候,咱经常会用到各种各样的外部命令。像 grep 用来查找文本内容,awk 处理文本,curl 发送HTTP请求等等。这些外部命令执行完之后,都会有一个状态码返回,这个状态码就相当于命令执行的一个“成绩单”,0 一般表示执行成功,非 0 就表示有问题了。

但是呢,很多时候我们在写脚本的时候,会忘记检查这些外部命令的状态码。这就好比老师发了成绩单,咱却不看,结果一些隐藏的问题就被忽略过去了。时间一长,脚本运行的时候就可能会出现一些莫名其妙的错误,而且很难找到根源。接下来,咱就来详细说说这个问题以及怎么解决它。

二、问题示例

Shell 技术栈示例

下面是一个简单的 Shell 脚本示例,这里面就存在依赖外部命令状态码检查遗漏的问题。

#!/bin/bash
# 尝试创建一个目录
mkdir my_directory
# 向一个文件中写入内容,假设这个文件在刚创建的目录里
echo "Hello, World!" > my_directory/test.txt
# 读取这个文件的内容
cat my_directory/test.txt

在这个脚本里,mkdir 命令用于创建一个新的目录。但是,我们并没有检查这个命令是否执行成功。如果因为权限不足或者目录已经存在等原因,mkdir 命令执行失败了,那么后面的 echo 命令和 cat 命令就会因为找不到 my_directory 这个目录而报错。可我们从脚本里很难一下子就发现问题出在 mkdir 命令上。

三、应用场景分析

自动化部署脚本

在自动化部署应用程序的时候,Shell 脚本会调用很多外部命令,比如解压文件、安装依赖、启动服务等。如果在解压文件的时候出现问题,但是脚本没有检查解压命令的状态码,就会继续执行后面的安装依赖和启动服务的操作。这样一来,后面的步骤肯定会失败,而且很难定位到是解压这一步出了问题。

数据处理脚本

当我们用 Shell 脚本处理大量数据的时候,会用到很多文本处理命令。例如,把一个大文件里的数据按照特定规则筛选出来并保存到新文件中。如果筛选命令执行失败了,但是没有检查状态码,后面基于这些错误数据进行的其他操作就会变得毫无意义。

四、不检查状态码的技术缺点

错误难以定位

就像前面说的那个创建目录的例子,如果不检查 mkdir 命令的状态码,当后面的操作出错时,我们很难知道是哪个环节出了问题。可能要花大量的时间和精力去排查,非常影响开发和维护的效率。

脚本的健壮性差

脚本在运行过程中可能会遇到各种各样的问题,比如文件不存在、权限不足、网络连接失败等。如果不检查外部命令的状态码,脚本就无法及时发现这些问题并做出相应的处理。这样一来,脚本在遇到问题时就很容易崩溃,无法稳定运行。

五、解决方法

手动检查状态码

我们可以在每个外部命令执行之后,手动检查它的状态码。如果状态码不是 0,就说明命令执行失败了,这时候可以输出错误信息并终止脚本的执行。

#!/bin/bash
# 尝试创建一个目录
mkdir my_directory
# 检查 mkdir 命令的状态码
if [ $? -ne 0 ]; then
    echo "创建目录失败!"
    exit 1
fi
# 向一个文件中写入内容,假设这个文件在刚创建的目录里
echo "Hello, World!" > my_directory/test.txt
# 检查 echo 命令的状态码
if [ $? -ne 0 ]; then
    echo "写入文件失败!"
    exit 1
fi
# 读取这个文件的内容
cat my_directory/test.txt
# 检查 cat 命令的状态码
if [ $? -ne 0 ]; then
    echo "读取文件失败!"
    exit 1
fi

在这个脚本里,$? 表示上一个命令的状态码。通过 if [ $? -ne 0 ] 来判断命令是否执行成功,如果失败就输出错误信息并使用 exit 1 终止脚本。

使用 set -e

在 Shell 脚本的开头加上 set -e 命令,这样当脚本里的任何一个命令执行失败(状态码非 0)时,脚本就会立即终止执行。

#!/bin/bash
# 当命令执行失败时,立即终止脚本
set -e
# 尝试创建一个目录
mkdir my_directory
# 向一个文件中写入内容,假设这个文件在刚创建的目录里
echo "Hello, World!" > my_directory/test.txt
# 读取这个文件的内容
cat my_directory/test.txt

使用 set -e 可以让脚本更加健壮,一旦出现问题就及时停止,避免错误进一步扩大。不过要注意,有些命令即使执行失败,状态码也可能是 0,这时候 set -e 就不起作用了。

六、注意事项

set -e 的局限性

虽然 set -e 能让脚本在遇到错误时及时终止,但是它也有一些局限性。比如,在 ifwhilefor 等语句块里的命令,即使执行失败,脚本也不会终止。

#!/bin/bash
# 当命令执行失败时,立即终止脚本
set -e
# 这个命令会失败,但是因为在 if 语句块里,脚本不会终止
if false; then
    non_existent_command
fi

在这个例子中,non_existent_command 是一个不存在的命令,肯定会执行失败。但是由于它在 if 语句块里,set -e 不会让脚本终止。

手动检查状态码的繁琐性

手动检查状态码虽然能准确地控制脚本的执行流程,但是会让脚本变得冗长和复杂。尤其是在脚本里有很多外部命令的时候,每个命令后面都要加上状态码检查的代码,会增加代码的维护难度。

七、总结

在编写 Shell 脚本的时候,依赖外部命令状态码检查遗漏是一个很容易出现但又危害很大的问题。它会让错误难以定位,降低脚本的健壮性。我们可以通过手动检查状态码和使用 set -e 这两种方法来解决这个问题。手动检查状态码能让我们更精确地控制脚本的执行流程,但是会增加代码的复杂度;而 set -e 能让脚本在遇到错误时及时终止,不过它有一定的局限性。在实际开发中,我们要根据具体的需求和场景,选择合适的方法来检查外部命令的状态码,这样才能保证 Shell 脚本的稳定性和可靠性。