如何在运行之前验证 Linux Bash 脚本的语法

已发表: 2022-06-16
红色背景下笔记本电脑屏幕上的 Linux 终端。
fatmawati achmad zaenuri/Shutterstock

Linux Bash 脚本中的错误和拼写错误会在脚本运行时造成可怕的后果。 这里有一些方法可以在你运行脚本之前检查它们的语法。

那些讨厌的虫子

写代码很难。 或者更准确地说,编写无错误的非平凡代码很难。 并且程序或脚本中的代码行越多,其中存在错误的可能性就越大。

您使用的编程语言对此有直接影响。 汇编编程比 C 编程困难得多,而 C 编程比 Python 编程更具挑战性。 你编程的语言越低级,你自己做的工作就越多。 Python 可能喜欢内置的垃圾收集例程,但 C 和汇编肯定不喜欢。

什么是“计算机错误”,该术语从何而来?
相关什么是“计算机错误”以及该术语从何而来?

编写 Linux shell 脚本有其自身的挑战。 使用像 C 这样的编译语言,称为编译器的程序会读取您的源代码(您在文本文件中键入的人类可读指令)并将其转换为二进制可执行文件。 二进制文件包含计算机可以理解和操作的机器代码指令。

如果编译器正在读取和解析的源代码符合语言的语法和其他规则,编译器才会生成二进制文件。 如果您拼写错误的保留字(语言的命令字之一)或变量名,编译器将抛出错误。

如何在 Bash 中使用变量
相关如何在 Bash 中使用变量

例如,有些语言坚持在使用变量之前声明它,而另一些语言则不那么挑剔。 如果您使用的语言要求您声明变量但您忘记这样做,编译器将抛出不同的错误消息。 尽管这些编译时错误很烦人,但它们确实会发现很多问题并迫使您解决它们。 但是,即使您的程序没有语法错误,也不意味着其中没有错误。 离得很远。

广告

由于逻辑缺陷导致的错误通常更难发现。 如果您告诉您的程序添加二和三,但您真的希望它添加二和二,您将不会得到您期望的答案。 但是程序正在做它被编写要做的事情。 程序的组成或语法没有任何问题。 问题是你。 您编写了一个格式良好的程序,但它并没有按照您的意愿行事。

测试很困难

彻底测试一个程序,即使是一个简单的程序,也是非常耗时的。 运行几次是不够的; 您确实需要测试代码中的所有执行路径,以便验证代码的所有部分。 如果程序要求输入,您需要提供足够范围的输入值来测试所有条件——包括不可接受的输入。

对于高级语言,单元测试和自动化测试有助于使彻底的测试成为可管理的练习。 所以问题是,有没有什么工具可以帮助我们编写无错误的 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 测试脚本的语法

广告

这是一个“没有消息就是好消息”的案例。 默默地让我们回到命令提示符是 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 

在 Ubuntu 上安装 shellcheck

要在 Fedora 上安装 ShellCheck,请使用此命令。 请注意,包名称是混合大小写的,但是当您在终端窗口中发出命令时,它都是小写的。

 须藤 dnf 安装 ShellCheck 

在 Fedora 上安装 shellcheck

在 Manjaro 和类似的基于 Arch 的发行版上,我们使用pacman

 sudo pacman -S shellcheck 

在 Manjaro 上安装 shellcheck

使用 ShellCheck

让我们尝试在我们的脚本上运行 ShellCheck。

 shellcheck seasons.sh 

使用 ShellCheck 检查脚本

ShellCheck 发现问题并将其报告给我们,并提供一组链接以获取更多信息。 如果您右键单击一个链接并从出现的上下文菜单中选择“打开链接”,该链接将在您的浏览器中打开。

ShellCheck 报告错误和警告

ShellCheck 还发现了另一个不那么严重的问题。 它以绿色文本报告。 这表明这是一个警告,而不是彻底的错误。

让我们纠正我们的错误并替换缺少的“[。” 一种错误修复策略是首先纠正最高优先级的问题,然后再处理较低优先级的问题,例如稍后的警告。

我们替换了缺失的“[”并再次运行 ShellCheck。

 shellcheck seasons.sh 

使用 ShellCheck 再次检查脚本

广告

ShellCheck 的唯一输出是指我们之前的警告,所以这很好。 我们没有需要修复的高优先级问题。

警告告诉我们,使用没有-r (按原样读取)选项的read命令将导致输入中的任何反斜杠被视为转义字符。 这是 linter 可以生成的迂腐输出类型的一个很好的例子。 在我们的例子中,用户无论如何都不应该输入反斜杠——我们需要他们输入一个数字。

像这样的警告需要程序员进行判断。 努力修复它,还是让它保持原样? 这是一个简单的两秒钟修复。 它会阻止警告使 ShellCheck 的输出变得混乱,所以我们不妨接受它的建议。 我们将添加一个“r”来选择read命令上的标志,并保存脚本。

 read -pr "输入月份(1 到 12):" 月份

再次运行 ShellCheck 为我们提供了一份干净的健康账单。

ShellCheck 未报告任何错误或警告

ShellCheck 是您的朋友

ShellCheck 可以检测、报告和建议各种问题。 查看他们的错误代码库,其中显示了它可以检测到多少类型的问题。

它是免费的、快速的,并且减轻了编写 shell 脚本的痛苦。 有什么不喜欢的?