如何在 Linux 中使用双括号条件测试

条件测试根据逻辑表达式的结果对 Linux Bash 脚本的执行流程进行分支。双括号条件测试大大简化了语法——但仍然有它们自己的陷阱。
单括号和双括号
Bash 提供了 test
命令。这使您可以测试逻辑表达式。该表达式将返回一个指示真或假响应的答案。返回值为零表示真响应。除零以外的任何值都表示错误。
使用 &&
运算符在命令行上链接命令使用此功能。仅当前一个命令成功完成时才会执行命令。
如果测试为真,将打印“是”字样。
test 15 -eq 15 && echo "Yes"
test 14 -eq 15 && echo "Yes"

单括号条件测试模仿 test
命令。它们将表达式包裹在方括号“[ ]
”中,并像 test
命令一样运行。事实上,它们是同一个程序,由相同的源代码创建。唯一的操作区别是 test
版本和 [
版本如何处理帮助请求。
这是来自源代码:
/* Recognize --help or --version, but only when invoked in the
"[" form, when the last argument is not "]". Use direct
parsing, rather than parse_long_options, to avoid accepting
abbreviations. POSIX allows "[ --help" and "[ --version" to
have the usual GNU behavior, but it requires "test --help"
and "test --version" to exit silently with status 0. */
我们可以通过向 test
和 [
寻求帮助并检查发送给 Bash 的响应代码来查看其效果。
test --help
echo $?
[ --help
echo $?

test
和 [
都是 shell builtins,这意味着它们被直接嵌入到 Bash 中。但也有 [
的独立二进制版本。
type test
type [
whereis [

相比之下,双括号条件测试 [[
和 ]]
是关键字。 [[
和 ]]
也执行逻辑测试,但它们的语法不同。因为它们是关键字,所以您可以使用一些在单括号版本中无法使用的巧妙功能。
Bash 支持双括号关键字,但它们在所有其他 shell 中都不可用。例如,Korn shell 支持它们,但普通的旧 shell sh 不支持。我们所有的脚本都以以下行开头:
#!/bin/bash
这确保我们调用 Bash shell 来运行脚本。
内置函数和关键字
我们可以使用 compgen
程序来列出内置函数:
compgen -b | fmt -w 70
如果不通过 fmt
管道输出,我们会得到一个长列表,每个内置函数都占一行。在这种情况下,将内置函数组合成一个段落会更方便。

我们可以在列表中看到test
和[
,但是没有列出]
。 [
命令查找结束符 ]
以检测它何时到达表达式的末尾,但 ]
不是单独的内置函数。这只是我们给 [
的一个信号,表示参数列表结束。
要查看关键字,我们可以使用:
compgen -k | fmt -w 70

[[
和 ]]
关键字都在列表中,因为 [[
是一个关键字,而 ]]
是另一个。它们是一对匹配的,就像 if
和 fi
以及 case
和 esac
一样。
当 Bash 解析脚本或命令行并检测到具有匹配的关闭关键字时,它会收集出现在它们之间的所有内容并应用关键字支持的任何特殊处理。
对于内置命令,内置命令后面的内容将像传递给任何其他命令行程序的参数一样传递给它。这意味着脚本作者必须特别注意变量值中的空格等问题。
外壳 Globbing
双括号条件测试可以使用 shell globbing。这意味着星号“*
”将扩展为“任何内容”。
在编辑器中键入或复制以下文本,并将其保存到名为“whelkie.sh”的文件中。
#!/bin/bash
stringvar="Whelkie Brookes"
if [[ "$stringvar" == *elk* ]];
then
echo "Warning contains seafood"
else
echo "Free from molluscs"
fi
要使脚本可执行,我们需要使用带有 -x
(执行)选项的 chmod
命令。如果您想尝试一下,您需要对本文中的所有脚本执行此操作。
chmod +x whelkie.sh

当我们运行脚本时,我们看到在字符串“Whelkie”中找到了字符串“elk”,而不管它周围有什么其他字符。
./whelkie.sh

需要注意的一点是,我们没有将搜索字符串用双引号引起来。如果你这样做,通配就不会发生。搜索字符串将按字面意思处理。
允许使用其他形式的 shell globbing。问号“?
”将匹配单个字符,单个方括号用于指示字符范围。例如,如果您不知道使用哪种情况,则可以用一个范围来涵盖这两种情况。
#!/bin/bash
stringvar="Jean-Claude van Clam"
if [[ "$stringvar" == *[cC]lam* ]];
then
echo "Warning contains seafood."
else
echo "Free from molluscs."
fi
将此脚本保存为“damme.sh”并使其可执行。当我们运行它时,条件语句解析为真,并且执行 if 语句的第一个子句。
./damme.sh

引用字符串
我们之前提到过用双引号包裹字符串。如果这样做,则不会发生 shell globbing。尽管约定俗成,但在使用 [[
和 ]]
时,您不需要将字符串变量括在引号中,即使它们包含空格。看下一个例子。 $stringvar
和 $surname
字符串变量都包含空格,但条件语句中都没有引用。
#!/bin/bash
stringvar="van Damme"
surname="van Damme"
if [[ $stringvar == $surname ]];
then
echo "Surnames match."
else
echo "Surnames don't match."
fi
将其保存到名为“surname.sh”的文件中并使其可执行。运行它使用:
./surname.sh

尽管两个字符串都包含空格,但脚本会成功并且条件语句解析为 true。这在处理包含空格的路径和目录名称时很有用。此处,如果变量包含有效的目录名称,则 -d
选项返回 true。
#!/bin/bash
dir="/home/dave/Documents/Needs Work"
if [[ -d ${dir} ]];
then
echo "Directory confirmed"
else
echo "Directory not found"
fi
如果您更改脚本中的路径以反映您自己计算机上的目录,将文本保存到名为“dir.sh”的文件中并使其可执行,您可以看到这有效。
./dir.sh

文件名 Globbing 陷阱
[ ]
和 [[ ]]
之间一个有趣的区别与文件名中的通配符有关。 “*.sh”形式将匹配所有脚本文件。除非存在单个脚本文件,否则使用单括号 [ ]
会失败。查找多个脚本会引发错误。
这是带有单括号条件的脚本。
#!/bin/bash
if [ -a *.sh ];
then
echo "Found a script file"
else
echo "Didn't find a script file"
fi
我们将此文本保存到“script.sh”中并使其可执行。我们检查了目录中有多少脚本,然后运行了脚本。
ls
./script.sh

Bash 抛出一个错误。我们删除了除一个脚本文件之外的所有文件,然后再次运行该脚本。
ls
./script.sh

条件测试返回 true 并且脚本不会导致错误。编辑脚本以使用双括号提供了第三种行为。
#!/bin/bash
if [[ -a *.sh ]];
then
echo "Found a script file"
else
echo "Didn't find a script file"
fi
我们将其保存到名为“dscript.sh”的文件中并使其可执行。在包含许多脚本的目录中运行此脚本不会引发错误,但脚本无法识别任何脚本文件。
使用双括号的条件语句只有在目录中有一个实际名为“*.sh”的文件的不太可能的情况下才会解析为真。
./dscript.sh

逻辑与和或
双括号让您可以使用 &&
和 ||
作为逻辑 AND 和 OR 运算符。
此脚本应将条件语句解析为真,因为 10 确实等于 10 并且 25 小于 26。
#!/bin/bash
first=10
second=25
if [[ first -eq 10 && second -lt 26 ]];
then
echo "Condition met"
else
echo "Condition failed"
fi
将此文本保存到名为“and.sh”的文件中,使其可执行,然后运行它:
./and.sh

脚本按照我们的预期执行。
这次我们将使用 ||
运算符。条件语句应该解析为真,因为虽然 10 不大于 15,但 25 仍然小于 26。只要第一个比较或第二个比较为真,整个条件语句解决为真。
将此文本保存为“or.sh”并使其可执行。
#!/bin/bash
first=10
second=25
if [[ first -gt 15 || second -lt 26 ]];
then
echo "Condition met."
else
echo "Condition failed."
fi
./or.sh

正则表达式
双括号条件语句允许使用 =~
运算符,它将字符串中的正则表达式搜索模式应用于语句的另一半。如果满足正则表达式,则条件语句被认为是真实的。如果正则表达式找不到匹配项,则条件语句解析为 false。
将此文本保存到名为“regex.sh”的文件中,并使其可执行。
#!/bin/bash
words="one two three"
WordsandNumbers="one 1 two 2 three 3"
email="dave@fabricateddomain.co.uk"
mask1="[0-9]"
mask2="[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,4}"
if [[ $words =~ $mask1 ]];
then
echo "\"$words\" contains digits."
else
echo "No digits found in \"$words\"."
fi
if [[ $WordsandNumbers =~ $mask1 ]];
then
echo "\"$WordsandNumbers\" contains digits."
else
echo "No digits found in \"$WordsandNumbers\"."
fi
if [[ $email =~ $mask2 ]];
then
echo "\"$email\" is a valid e-mail address."
else
echo "Couldn't parse \"$email\"."
fi
第一组双括号使用字符串变量 $mask1
作为正则表达式。这包含零到九范围内所有数字的模式。它将此正则表达式应用于 $words
字符串变量。
第二组双括号再次使用字符串变量 $mask1
作为正则表达式,但这次它与 $WordsandNumbers
字符串变量一起使用。
最后一组双括号在字符串变量 $mask2
中使用了更复杂的正则表达式掩码。
- [A-Za-z0-9._%+-]+:这匹配任何大写或小写字母字符,或从零到九的任何数字,或句点,下划线、百分号或加号或减号。 “
[]
”之外的“+
”表示对找到的所有字符重复这些匹配。 - @:这只匹配“@”字符。
- [A-Za-z0-9.-]+:这匹配任何大写或小写字母字符,或从零到九的任何数字,或句点或连字符。 “
[ ]
”之外的“+
”表示对找到的所有字符重复这些匹配。 - .:匹配“.”仅限字符。
- [A-Za-z]{2,4}:匹配任何大写或小写字母。 “
{2,4}
”表示至少匹配两个字符,最多匹配四个字符。
综上所述,正则表达式掩码将检查电子邮件地址的格式是否正确。
将脚本文本保存到名为“regex.sh”的文件中并使其可执行。当我们运行脚本时,我们得到这个输出。
./regex.sh

第一个条件语句失败,因为正则表达式正在寻找数字,但 $words
字符串变量中保存的值中没有数字。
第二个条件语句成功,因为 $WordsandNumbers
字符串变量确实包含数字。
最后的条件语句成功——也就是说,它解析为真——因为电子邮件地址的格式正确。
只有一个条件
双括号条件测试为您的脚本带来了灵活性和易读性。只要能够在条件测试中使用正则表达式,就可以证明学习如何使用 [[
和 ]]
。
只需确保脚本调用支持它们的 shell,例如 Bash。