如何使用 Shell 跟踪来跟踪 Shell 脚本中的命令执行
在Shell 脚本调试系列的这篇文章中,我们将讲解第三种 Shell 脚本调试模式,即 Shell 跟踪,并通过一些示例来演示其工作原理以及如何使用它。
本系列的前一部分清楚地阐明了另外两种 shell 脚本调试模式:详细模式和语法检查模式,并提供了易于理解的示例,说明如何在这些模式下启用 shell 脚本调试。
Shell 跟踪简单地意味着跟踪 shell 脚本中命令的执行。要打开 shell 跟踪,请使用-x
调试选项。
这将指示 shell 在执行所有命令时在终端上显示它们及其参数。
我们将使用sys_info.sh
下面的 shell 脚本,该脚本简要打印系统日期和时间、登录用户数和系统正常运行时间。但是,它包含语法错误,我们需要查找并更正。
#!/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 命令运行它,如下所示:
$ chmod +x sys_info.sh $ sudo bash -x sys_info.sh
从上面的输出中,我们可以观察到,命令首先被执行,然后它的输出被替换为变量的值。
例如,首先执行日期并将其输出替换为变量DATE的值。
我们可以执行语法检查以仅显示语法错误,如下所示:
$ sudo bash -n sys_info.sh
如果我们仔细查看 shell 脚本,我们会发现缺少if statement
一个结束语fi
。因此,让我们添加它,新脚本现在应如下所示:
#!/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 身份调用它并进行一些语法检查:
$ sudo bash -n sys_info.sh
上面的语法检查操作的结果仍然显示,我们的脚本在第 21 行还有一个错误。因此,我们仍然需要进行一些语法更正。
如果我们再分析一下脚本,第 21 行的错误是由于函数内部的(”)
最后一个echo 命令缺少结束双引号造成的print_sys_info
。
我们将在echo命令中添加结束的双引号并保存文件。更改后的脚本如下:
#!/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
现在再一次从语法上检查该脚本。
$ sudo bash -n sys_info.sh
上面的命令不会产生任何输出,因为我们的脚本现在语法正确。我们也可以再次跟踪脚本的执行情况,它应该可以正常工作:
$ sudo bash -x sys_info.sh
现在运行脚本。
$ sudo ./sys_info.sh
Shell 脚本执行跟踪的重要性
check_root
Shell 脚本跟踪可以帮助我们识别语法错误,更重要的是逻辑错误。以 Shell 脚本中的函数为例sys_info.sh
,该函数旨在确定用户是否为 root,因为该脚本仅允许超级用户执行。
check_root(){ if [ "$UID" -ne "$ROOT_ID" ]; then echo "You are not allowed to execute this program!" exit 1; fi }
这里的魔法是由if statement
表达式控制的[ "$UID" -ne "$ROOT_ID" ]
,一旦我们没有使用合适的数值运算符(-ne
在本例中意味着不等于),我们最终可能会遇到逻辑错误。
假设我们使用-eq
(表示等于),这将允许任何系统用户以及根用户运行该脚本,因此是一个逻辑错误。
check_root(){ if [ "$UID" -eq "$ROOT_ID" ]; then echo "You are not allowed to execute this program!" exit 1; fi }
注意:正如我们在本系列开始时所看到的,set shell 内置命令可以在 shell 脚本的特定部分激活调试。
因此,下面这一行将帮助我们通过跟踪函数的执行来找到函数中的逻辑错误:
存在逻辑错误的脚本:
#!/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,不等于 root 的ROOT_ID,后者为0。
$ ./sys_info.sh
好了,现在就是这样了,我们已经到了shell 脚本调试系列的结尾,下面的回复表可用于解决有关本指南或整个 3 部分系列的任何问题或反馈。