舫摘

知人者智 自知者明 胜人者有力 自胜者强

0%

如何在 Shell 脚本中跟踪调试命令的执行

shell 脚本调试系列 中,本文将解释第三种 shell 脚本调试模式,即 shell 跟踪,并查看一些示例来演示它如何工作以及如何使用它。

本系列的前面部分清晰地阐明了另外两种 shell 脚本调试模式:详细模式和语法检查模式,并用易于理解的例子展示了如何在这些模式下启用 shell 脚本调试。

shell 跟踪简单的来说就是跟踪 shell 脚本中的命令的执行。要打开 shell 跟踪,请使用 -x 调试选项。

这会让 shell 在终端上显示所有执行的命令及其参数。

我们将使用下面的 sys_info.sh shell 脚本,它会简要地打印出你的系统日期和时间、登录的用户数和系统的运行时间。不过,脚本中包含我们需要查找和更正的语法错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
# script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME
}
check_root
print_sys_info
exit 0

保存文件并执行脚本。脚本只能用root用户运行,因此如下使用sudo命令运行:

1
2
$ chmod +x sys_info.sh
$ sudo bash -x sys_info.sh

`shell`跟踪 - 显示脚本中的错误

从上面的输出我们可以观察到,首先执行命令,然后其输出做为一个变量的值。

例如,先执行date,其输出做为变量DATE的值。

我们可以执行语法检查来只显示其中的语法错误,如下所示:

1
$ sudo bash -n sys_info.sh

脚本中语法检查

如果我们审视这个 shell 脚本,我们就会发现if语句缺少了封闭条件的fi关键字。因此,让我们加上它,新的脚本应该看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME
}
check_root
print_sys_info
exit 0

再次保存文件并以root执行,同时做语法检查:

1
$ sudo bash -n sys_info.sh

在`shell`脚本中执行语法检查

上面的语法检查操作的结果仍然显示在脚本的第21行还有一个错误。所以,我们仍然要纠正一些语法。

再一次分析脚本,会发现第21行的错误是由于在print_sys_info函数内最后一个echo命令中没有闭合双引号"

我们将在echo命令中添加闭合双引号并保存文件。修改过的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME"
}
check_root
print_sys_info
exit 0

现在再一次检查语法。

1
$ sudo bash -n sys_info.sh

上面的命令不会产生任何输出,因为我们的脚本语法上正确。我们也可以再次跟踪脚本执行,它应该工作得很好:

1
$ sudo bash -x sys_info.sh

跟踪`shell`脚本执行

现在运行脚本。

1
$ sudo ./sys_info.sh

用`shell`脚本显示日期、时间和运行时间

shell 跟踪执行的重要性

shell 脚本跟踪可以帮助我们识别语法错误,更重要的是识别逻辑错误。例如,在sys_info.sh shell脚本中的check_root函数,它用于确定用户是否为root,因为脚本只允许由超级用户执行。

1
2
3
4
5
6
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}

这里的魔法是由if语句表达式["$ UID" -ne "$ ROOT_ID"]控制的,一旦我们不使用合适的数字运算符(示例中为-ne,这意味着不相等),我们最终可能会出一个逻辑错误。

假设我们使用-eq(意思是等于),这将允许任何系统用户以及root用户运行脚本,因此是一个逻辑错误。

1
2
3
4
5
6
check_root(){
if [ "$UID" -eq "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}

注意:我们在本系列开头介绍过,set这个shell内置命令可以在shell脚本的特定部分激活调试。

因此,下面的行将帮助我们通过跟踪脚本的执行在其中找到这个逻辑错误:

具有逻辑错误的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -eq "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime : $UPTIME"
}
#turning on and off debugging of check_root function
set -x ; check_root; set +x ;
print_sys_info
exit 0

保存文件并调用脚本,在输出中,我们可以看到一个普通系统用户可以在未sudo的情况下运行脚本。 这是因为USER_ID的值为100,不等于为0rootROOT_ID

1
$ ./sys_info.sh

未`sudo`的情况下运行`shell`脚本

那么,现在我们已经完成了shell脚本调试系列,可以在下面的反馈栏里给我们关于本篇或者本系列提出问题或反馈。