如何在运行之前验证 Linux Bash 脚本的语法
已发表: 2022-06-16Linux Bash 脚本中的错误和拼写错误会在脚本运行时造成可怕的后果。 这里有一些方法可以在你运行脚本之前检查它们的语法。
那些讨厌的虫子
写代码很难。 或者更准确地说,编写无错误的非平凡代码很难。 并且程序或脚本中的代码行越多,其中存在错误的可能性就越大。
您使用的编程语言对此有直接影响。 汇编编程比 C 编程困难得多,而 C 编程比 Python 编程更具挑战性。 你编程的语言越低级,你自己做的工作就越多。 Python 可能喜欢内置的垃圾收集例程,但 C 和汇编肯定不喜欢。
编写 Linux shell 脚本有其自身的挑战。 使用像 C 这样的编译语言,称为编译器的程序会读取您的源代码(您在文本文件中键入的人类可读指令)并将其转换为二进制可执行文件。 二进制文件包含计算机可以理解和操作的机器代码指令。
如果编译器正在读取和解析的源代码符合语言的语法和其他规则,编译器才会生成二进制文件。 如果您拼写错误的保留字(语言的命令字之一)或变量名,编译器将抛出错误。
例如,有些语言坚持在使用变量之前声明它,而另一些语言则不那么挑剔。 如果您使用的语言要求您声明变量但您忘记这样做,编译器将抛出不同的错误消息。 尽管这些编译时错误很烦人,但它们确实会发现很多问题并迫使您解决它们。 但是,即使您的程序没有语法错误,也不意味着其中没有错误。 离得很远。
由于逻辑缺陷导致的错误通常更难发现。 如果您告诉您的程序添加二和三,但您真的希望它添加二和二,您将不会得到您期望的答案。 但是程序正在做它被编写要做的事情。 程序的组成或语法没有任何问题。 问题是你。 您编写了一个格式良好的程序,但它并没有按照您的意愿行事。
测试很困难
彻底测试一个程序,即使是一个简单的程序,也是非常耗时的。 运行几次是不够的; 您确实需要测试代码中的所有执行路径,以便验证代码的所有部分。 如果程序要求输入,您需要提供足够范围的输入值来测试所有条件——包括不可接受的输入。
对于高级语言,单元测试和自动化测试有助于使彻底的测试成为可管理的练习。 所以问题是,有没有什么工具可以帮助我们编写无错误的 Bash shell 脚本?
答案是肯定的,包括 Bash shell 本身。
使用 Bash 检查脚本语法
Bash -n
(noexec) 选项告诉 Bash 读取脚本并检查它的语法错误,而不运行脚本。 根据您的脚本打算做什么,这可能比运行它并寻找问题更安全。
这是我们要检查的脚本。 它并不复杂,主要是一组if
语句。 它提示并接受一个代表一个月的数字。 脚本决定该月属于哪个季节。 显然,如果用户根本没有提供输入,或者他们提供了无效输入(如字母而不是数字),这将不起作用。
#! /bin/bash read -p "输入月份(1到12):"月份 # 他们输入了什么吗? 如果 [ -z "$month" ] 然后 echo "您必须输入一个代表月份的数字。" 1号出口 菲 # 月份有效吗? if (( "$month" < 1 || "$month" > 12)); 然后 echo "月份必须是 1 到 12 之间的数字。" 出口 0 菲 #现在是春节吗? if (( "$month" >= 3 && "$month" < 6)); 然后 回声“那是一个春天的月份。” 出口 0 菲 #现在是暑假吗? if (( "$month" >= 6 && "$month" < 9)); 然后 回声“那是一个夏天的月份。” 出口 0 菲 #现在是秋天吗? if (( "$month" >= 9 && "$month" < 12)); 然后 echo "那是秋天的月份。" 出口 0 菲 # 一定是冬月 回声“那是一个冬天的月份。” 出口 0
此部分检查用户是否输入了任何内容。 它测试$month
变量是否未设置。
如果 [ -z "$month" ] 然后 echo "您必须输入一个代表月份的数字。" 1号出口 菲
此部分检查他们是否输入了 1 到 12 之间的数字。它还捕获不是数字的无效输入,因为字母和标点符号不会转换为数值。
# 月份有效吗? if (( "$month" < 1 || "$month" > 12)); 然后 echo "月份必须是 1 到 12 之间的数字。" 出口 0 菲
所有其他 If 子句检查$month
变量中的值是否介于两个值之间。 如果是,则该月属于该季节。 例如,如果用户输入的月份是 6、7 或 8,则它是夏季月份。
#现在是暑假吗? if (( "$month" >= 6 && "$month" < 9)); 然后 回声“那是一个夏天的月份。” 出口 0 菲
如果您想完成我们的示例,请将脚本文本复制并粘贴到编辑器中,并将其保存为“seasons.sh”。 然后使用chmod
命令使脚本可执行:
chmod +x seasons.sh
我们可以通过以下方式测试脚本
- 根本不提供任何输入。
- 提供非数字输入。
- 提供 1 到 12 范围之外的数值。
- 提供 1 到 12 范围内的数值。
在所有情况下,我们都使用相同的命令启动脚本。 唯一的区别是用户在脚本提升时提供的输入。
./seasons.sh
这似乎按预期工作。 让 Bash 检查我们脚本的语法。 我们通过调用-n
(noexec) 选项并传入我们的脚本名称来做到这一点。
bash -n ./seasons.sh
这是一个“没有消息就是好消息”的案例。 默默地让我们回到命令提示符是 Bash 表示一切似乎都很好的方式。 让我们破坏我们的脚本并引入一个错误。
我们将从第一个if
子句中删除then
。
# 月份有效吗? if (( "$month" < 1 || "$month" > 12)); # "then" 已被删除 echo "月份必须是 1 到 12 之间的数字。" 出口 0 菲
现在让我们运行脚本,首先没有用户输入,然后有用户输入。
./seasons.sh
第一次运行脚本时,用户没有输入值,因此脚本终止。 我们破坏的部分永远不会到达。 脚本结束时没有来自 Bash 的错误消息。
第二次运行脚本时,用户提供一个输入值,并执行第一个 if 子句以检查用户输入的完整性。 这会触发来自 Bash 的错误消息。
请注意,Bash 检查该子句的语法——以及所有其他代码行——因为它不关心脚本的逻辑。 当 Bash 检查脚本时,不会提示用户输入数字,因为脚本没有运行。
脚本的不同可能执行路径不会影响 Bash 检查语法的方式。 Bash 简单而有条不紊地从脚本顶部到底部运行,检查每一行的语法。
ShellCheck 实用程序
linter(以 Unix 鼎盛时期的 C 源代码检查工具命名)是一种代码分析工具,用于检测编程错误、风格错误以及对该语言的可疑或可疑使用。 Linter 可用于许多编程语言,并以迂腐着称。 并非 linter 发现的所有内容本身都是错误,但它们引起您注意的任何内容都可能值得关注。
ShellCheck 是一个用于 shell 脚本的代码分析工具。 它的行为就像 Bash 的 linter。
让我们将丢失的保留字放回我们的脚本中, then
尝试其他方法。 我们将从第一个if
子句中删除左括号“[”。
# 他们输入了什么吗? if -z "$month" ] # 左括号 "[" 被移除 然后 echo "您必须输入一个代表月份的数字。" 1号出口 菲
如果我们使用 Bash 检查脚本,它不会发现问题。
bash -n seasons.sh
./seasons.sh
但是当我们尝试运行脚本时,我们会看到一条错误消息。 而且,尽管出现错误消息,脚本仍会继续执行。 这就是为什么一些错误如此危险的原因。 如果在脚本中进一步采取的操作依赖于用户的有效输入,则脚本的行为将是不可预测的。 它可能会使数据面临风险。
Bash -n
(noexec) 选项在脚本中找不到错误的原因是左括号“[”是一个名为[
的外部程序。 它不是 Bash 的一部分。 这是使用test
命令的一种简写方式。
Bash 在验证脚本时不会检查外部程序的使用。
安装 ShellCheck
ShellCheck 需要安装。 要在 Ubuntu 上安装它,请键入:
sudo apt install shellcheck
要在 Fedora 上安装 ShellCheck,请使用此命令。 请注意,包名称是混合大小写的,但是当您在终端窗口中发出命令时,它都是小写的。
须藤 dnf 安装 ShellCheck
在 Manjaro 和类似的基于 Arch 的发行版上,我们使用pacman
:
sudo pacman -S shellcheck
使用 ShellCheck
让我们尝试在我们的脚本上运行 ShellCheck。
shellcheck seasons.sh
ShellCheck 发现问题并将其报告给我们,并提供一组链接以获取更多信息。 如果您右键单击一个链接并从出现的上下文菜单中选择“打开链接”,该链接将在您的浏览器中打开。
ShellCheck 还发现了另一个不那么严重的问题。 它以绿色文本报告。 这表明这是一个警告,而不是彻底的错误。
让我们纠正我们的错误并替换缺少的“[。” 一种错误修复策略是首先纠正最高优先级的问题,然后再处理较低优先级的问题,例如稍后的警告。
我们替换了缺失的“[”并再次运行 ShellCheck。
shellcheck seasons.sh
ShellCheck 的唯一输出是指我们之前的警告,所以这很好。 我们没有需要修复的高优先级问题。
警告告诉我们,使用没有-r
(按原样读取)选项的read
命令将导致输入中的任何反斜杠被视为转义字符。 这是 linter 可以生成的迂腐输出类型的一个很好的例子。 在我们的例子中,用户无论如何都不应该输入反斜杠——我们需要他们输入一个数字。
像这样的警告需要程序员进行判断。 努力修复它,还是让它保持原样? 这是一个简单的两秒钟修复。 它会阻止警告使 ShellCheck 的输出变得混乱,所以我们不妨接受它的建议。 我们将添加一个“r”来选择read
命令上的标志,并保存脚本。
read -pr "输入月份(1 到 12):" 月份
再次运行 ShellCheck 为我们提供了一份干净的健康账单。
ShellCheck 是您的朋友
ShellCheck 可以检测、报告和建议各种问题。 查看他们的错误代码库,其中显示了它可以检测到多少类型的问题。
它是免费的、快速的,并且减轻了编写 shell 脚本的痛苦。 有什么不喜欢的?