怎样在运行之前验证 Linux Bash 脚本的语法

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

那些讨厌的虫子

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

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

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

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

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


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

测试很困难

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

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

答案是肯定的,包括 Bash shell 本身。

使用 Bash 检查脚本语法

重击 -n (noexec) 选项告诉 Bash 读取脚本并检查它的语法错误,而不运行脚本。 根据您的脚本打算做什么,这可能比运行它并寻找问题更安全。

这是我们要检查的脚本。 并不复杂,主要是一组 if 陈述。 它提示并接受一个代表一个月的数字。 脚本决定该月属于哪个季节。 显然,如果用户根本没有提供输入,或者他们提供了无效输入(如字母而不是数字),这将不起作用。

#! /bin/bash

read -p "Enter a month (1 to 12): " month

# did they enter anything?
if [ -z "$month" ]
then
  echo "You must enter a number representing a month."
  exit 1
fi

# is it a valid month?
if (( "$month" < 1 || "$month" > 12)); then
  echo "The month must be a number between 1 and 12."
  exit 0
fi

# is it a Spring month?
if (( "$month" >= 3 && "$month" < 6)); then
  echo "That's a Spring month."
  exit 0
fi

# is it a Summer month?
if (( "$month" >= 6 && "$month" < 9)); then
  echo "That's a Summer month."
  exit 0
fi

# is it an Autumn month?
if (( "$month" >= 9 && "$month" < 12)); then
  echo "That's an Autumn month."
  exit 0
fi

# it must be a Winter month
echo "That's a Winter month."
exit 0


此部分检查用户是否输入了任何内容。 它测试是否 $month 变量未设置。

if [ -z "$month" ]
then
  echo "You must enter a number representing a month."
  exit 1
fi

此部分检查他们是否输入了 1 到 12 之间的数字。它还捕获不是数字的无效输入,因为字母和标点符号不会转换为数值。

# is it a valid month?
if (( "$month" < 1 || "$month" > 12)); then
  echo "The month must be a number between 1 and 12."
  exit 0
fi

所有其他 If 子句检查 $month 变量在两个值之间。 如果是,则该月属于该季节。 为了 example,如果用户输入的月份是 6、7 或 8,则为夏季月份。

# is it a Summer month?
if (( "$month" >= 6 && "$month" < 9)); then
  echo "That's a Summer month."
  exit 0
fi

如果您想完成我们的示例,请将脚本文本复制并粘贴到编辑器中,并将其保存为“seasons.sh”。 然后使用 chmod 命令:

chmod +x seasons.sh

我们可以通过以下方式测试脚本

  • 根本不提供任何输入。
  • 提供非数字输入。
  • 提供 1 到 12 范围之外的数值。
  • 提供 1 到 12 范围内的数值。

在所有情况下,我们都使用相同的命令启动脚本。 唯一的区别是用户在脚本提升时提供的输入。

./seasons.sh

使用各种有效和无效输入测试脚本

这似乎按预期工作。 让 Bash 检查我们脚本的语法。 我们通过调用 -n (noexec) 选项并传入我们脚本的名称。

bash -n ./seasons.sh

使用 Bash 测试脚本的语法


这是一个“没有消息就是好消息”的案例。 默默地让我们回到命令提示符是 Bash 表示一切似乎都很好的方式。 让我们破坏我们的脚本并引入一个错误。

我们将删除 then 从一开始 if 条款。

# is it a valid month?
if (( "$month" < 1 || "$month" > 12)); # "then" has been removed
  echo "The month must be a number between 1 and 12."
  exit 0
fi

现在让我们运行脚本,首先没有用户输入,然后有用户输入。

./seasons.sh

使用无效和有效输入测试脚本

第一次运行脚本时,用户没有输入值,因此脚本终止。 我们破坏的部分永远不会到达。 脚本结束时没有来自 Bash 的错误消息。

第二次运行脚本时,用户提供一个输入值,并执行第一个 if 子句以检查用户输入的完整性。 这会触发来自 Bash 的错误消息。

请注意,Bash 检查该子句的语法——以及所有其他代码行——因为它不关心脚本的逻辑。 当 Bash 检查脚本时,不会提示用户输入数字,因为脚本没有运行。

脚本的不同可能执行路径不会影响 Bash 检查语法的方式。 Bash 简单而有条不紊地从脚本顶部到底部运行,检查每一行的语法。

ShellCheck 实用程序

linter(以 Unix 鼎盛时期的 C 源代码检查工具命名)是一种代码分析工具,用于检测编程错误、风格错误以及对该语言的可疑或可疑使用。 Linter 可用于许多编程语言,并以迂腐着称。 并非 linter 发现的所有内容本身都是错误,但它们引起您注意的任何内容都可能值得关注。


壳牌检查 是一个shell脚本的代码分析工具。 它的行为就像 Bash 的 linter。

让我们把我们的失踪 then 保留字回到我们的脚本中,然后尝试其他方法。 我们将从一开始就删除左括号“[”[”fromtheveryfirstif 条款。

# did they enter anything?
if -z "$month" ] # opening bracket "[" removed
then
  echo "You must enter a number representing a month."
  exit 1
fi

如果我们使用 Bash 检查脚本,它不会发现问题。

bash -n seasons.sh
./seasons.sh

来自通过语法检查但未检测到问题的脚本的错误消息

但是当我们尝试运行脚本时,我们会看到一条错误消息。 而且,尽管出现错误消息,脚本仍会继续执行。 这就是为什么一些错误如此危险的原因。 如果在脚本中进一步采取的操作依赖于用户的有效输入,则脚本的行为将是不可预测的。 它可能会使数据面临风险。

Bash 的原因 -n (noexec) 选项在脚本中找不到错误是左括号“[”是一个名为[”isanexternalprogramcalled[. 它不是 Bash 的一部分。 这是使用 test 命令。


Bash 在验证脚本时不会检查外部程序的使用。

安装 ShellCheck

ShellCheck 需要安装。 要在 Ubuntu 上安装它,请键入:

sudo apt install shellcheck

在 Ubuntu 上安装 shellcheck

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

sudo dnf install ShellCheck

安装 shellcheck Fedora

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

sudo pacman -S shellcheck

在 Manjaro 上安装 shellcheck

使用 ShellCheck

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

shellcheck seasons.sh

使用 ShellCheck 检查脚本

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

ShellCheck 报告错误和警告

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

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

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

shellcheck seasons.sh

使用 ShellCheck 再次检查脚本


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

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

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

read -pr "Enter a month (1 to 12): " month

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

ShellCheck 未报告任何错误或警告

ShellCheck 是您的朋友

ShellCheck 可以检测、报告和建议 一系列问题. 看看他们的 坏代码画廊,这表明它可以检测到多少种类型的问题。

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