Shell脚本中的条件表达式
1. 概述
当我们在命令行上工作时,我们可能需要一些程序仅在满足条件时才运行。让我们想象一个例子:根据文件的特性运行一个命令列表,假设:如果它存在,大小、名称、类型等。
GNU/Linux 为我们提供了一套非常强大的工具来帮助我们实现这一目标。
在本教程中,我们将讨论条件表达式,并构建一些示例来阐明这些表达式的用法。
2. 带有 && 和 || 的条件表达式
让我们记住,shell 中的每个命令都是条件表达式。我们知道,因为每个都返回一个整数,作为退出状态,表示成功 - 0 或失败 - 1。
当我们使用 shell 时,我们通常使用诸如“ ;、&、*&&*或 ||”之类的标记链接在一起运行多个指令。
** &&和|| 令牌允许我们借助它们的退出状态来连接我们的命令。
当且仅当左侧指令的退出状态为零时, *&&*标记链接执行右侧的命令。
|| 令牌,当且仅当左侧指令的退出状态不为零时,才链接执行右侧的命令。
让我们创建一个简单的示例来澄清:
$ (exit 0) && echo True
True
$ (exit 1) || echo False
False
现在我们了解了这些令牌,让我们看看如何使用它。
首先,让我们创建一个名为some_file的文件,其内容如下:
$ cat << __end > some_file
foo
this string exists
bar
__end
现在,使用grep 命令,让我们看看如果文件中存在字符串,我们如何显示文本:
$ grep -q "this string exists" some_file && { echo "Everything"; echo "is all right"; }
由于第一条指令的存在状态为 0,因此该命令行的输出将是:
Everything
is all right
现在,让我们尝试:
$ grep -q "this string doesn't exist" some_file || { echo "Not"; echo "today"; }
这一次,我们搜索的字符串在文件中不存在,所以第一条指令返回 1 - 失败退出状态。这意味着此命令行的输出将是:
Not
today
牢记这一点,它使我们认为我们可以通过将命令与这些标记链接起来来编写命令。
让我们尝试使用相同的文件和相同的命令:
$ grep -q "this string doesn't exist" some_file \
&& { echo "Everything"; echo "is all right"; } \
|| { echo "Not"; echo "today"; }
让我们仔细看看表达式。
在这里,我们使用-q*参数来获取退出状态,而不是 stdout 中的通常输出*。 使用该退出状态:
- 如果为 0(如果字符串在文件中),则*&&*标记右侧的命令列表将执行
- 如果退出状态不为0(如果字符串不在文件中),那么,* &&*右边的内容不会执行;而是令牌右侧的说明 || 将被执行
因此,此命令的结果将是:
Not
today
让我们看另一个使用这些标记链接命令的示例:
$ ( echo "Here 1"; exit 1 ) \
&& ( echo "Here 2" ) \
&& ( echo "Here 3" ) \
|| ( echo "Here 4"; exit 4 ) \
&& ( echo "Here 5"; exit 5 ) \
|| ( echo "Here 6"; exit 6 ) \
|| ( echo "Here 7"; exit 0 ) \
&& ( echo "Here 8" )
输出将是:
Here 1
Here 4
Here 6
Here 7
Here 8
总而言之,我们可以说*&&和||* 相当于 AND 和 OR 逻辑运算,允许我们控制指令流的过程。但是我们也可以(非常小心地)将它用作if-else语法。
3. 用if构建更复杂的表达式
在前面的示例中,我们看到由于命令是条件表达式这一事实,我们可以清楚地表达命令列表。但是,对于更复杂的命令,这种语法可能会很棘手。
为了让事情更清晰,并创造新的方式来表达指令流,我们的 shell 有关键字:if、then、elif、else、fi。
因此,例如,让我们使用 if、then、else和fi关键字:
$ if
( echo "Here 1" )
( echo "Here 2" )
( echo "Here 3" )
then
echo Inside the \"then\" sentence
else
echo Inside the \"else\" sentence
fi
输出将是:
Here 1
Here 2
Here 3
Inside the "then" sentence
现在,让我们尝试以下操作:
$ if
( echo "Here 1" )
( echo "Here 2"; exit 2 )
( echo "Here 3"; exit 3 )
then
echo Inside the \"then\" sentence
else
echo Inside the \"else\" sentence
fi
输出将是:
Here 1
Here 2
Here 3
Inside the "else" sentence
这是因为此语法侧重于最后执行的命令的状态。所以这就是为什么它在一个非零状态的命令之后打印“Here 3”的原因,也是shell进入else块的原因。
4. [, test, [[, 和 ((,
shell 通常配备了使用条件表达式的标记*[、test和[[*内置函数。
这些标记中的每一个都彼此不同,但我们稍后将讨论它们的区别。现在,让我们关注*[、test和[[的共同点。 对于我们的第一个示例,*让我们看看如何使用标准/home目录检查文件是否为目录:**
$ [ -d /home ] && echo "It's a directory"
It's a directory
在这里,我们使用了*-d*选项来检查文件是否存在并且是一个目录,并且我们用 [ 和 ] 标记包围了表达式。
接下来,让我们看另一个检查 2 个字符串是否相等的示例:
$ [ "this string" = "THIS STRING" ] \
&& echo They are equals \
|| echo They are different
输出将是:
They are different
当然,我们可以使用以下任何选项构建一个非常相似的比较整数的示例:“-eq”、“-ne”、“-lt”、“-le”、“-gt”或“-ge” ”。
让我们再次用 [] 标记包围这个表达式,并使用*-lt*选项比较一个整数是否小于另一个整数:
$ [ 3 -lt 6 ] && echo "It's less than"
It's less than
应该注意的是**,我们列出的示例可以使用*[、[[或test*命令中的任何一个来构建。**
现在,让我们深入了解每个令牌和命令的作用以及它们之间的区别。
4.1. [和test内置函数
** [和test是内置的,可以帮助我们执行我们在上一节中列出的操作。**
使用*[内置函数,我们可以包围一个条件表达式。这将具有与内置test*相同的有效性。
换句话说:
$ [ "string1" = "string2" ]
这几乎与以下内容相同:
$ test "string1" = "string2"
4.2. *[[*关键字
与之前的标记相比,[[ 是一个关键字,与内置的不同。
内置命令或函数将在 shell 中执行,而不是外部可执行文件。
关键字是 shell 认为特殊的词;因此,如果 shell 找到关键字[[,那么 shell 将寻找关闭的]]。**我们可以通过输入compgen -k 来列出它们。
此外,*[[*关键字是一些 shell 的扩展,例如 Bash。
4.3. *((*令牌
** ((是一些 shell 的扩展,它允许我们将表达式计算为算术表达式。
让我们看一个使用它的例子:
(( var < 10 ))
(( var++ ))
(( var=1; var<=10; var++ ))
如果表达式的值非零,则此运算符的退出状态为 0;否则,返回状态为 1。
4.4. *[[和[*之间的区别
让我们回顾一下*[[和[*之间的一些主要区别。
首先,正如我们在 4.1 和 4.2 部分中看到的,[[是我们 shell 的关键字,但是[ 和 test是 shell 内置的
另一个重要的区别是** [是 POSIX,而[[不是。
使用<或>运算符时,如果我们想在[中使用它们,我们需要转义它们:
$ [ "a" \< "b" ]
如果我们想在关键字*[[*中使用它们,情况并非如此。
另一个显着的区别是*[[关键字使用当前语言环境按字典顺序排序,而[*使用 ASCII 排序。
*接下来,*&&和 || 运算符在每对令牌中会有所不同。
虽然这会起作用:
$ [[ 1 = 1 && 2 = 2 ]] # returns True
这种情况不会:
$ [ 1 = 1 && 2 = 2 ] # returns a Bash error
但是我们可以通过执行以下操作以相同的方式操作:
$ [ 1 = 1 ] && [ 2 = 2 ]
这是因为,正如我们在 2.1 节中看到的:我们 shell 中的每个命令都是一个条件表达式。
换句话说:[ 1 = 1 ]返回true,在令牌*&&的帮助下,[ 2 = 2 ]将被执行并返回true*。然后,组合的句子将返回true。
这些标记在扩展时的分词和文件名生成方面也有所不同。
如果我们这样定义一个变量:
$ var="a b"
然后,这将返回true:
$ [[ $var = "a b" ]]
这会返回一个语法错误,因为这里扩展为*[ ab = ‘a b’ ]*:
$ [ $var = "a b" ]
bash: [: too many arguments
**标记“ =”的使用也不同:
让我们创建两个示例:
$ [ abc = a?? ] # returns False
$ [[ abc = a?? ]] # return True
这种行为是因为使用内置的*[,我们执行字符串比较。另一方面,当使用[[*关键字时,bash 执行模式匹配。
最后,让我们注意*[[有=~*运算符。
在使用[[标记时,我们可以使用=~标记,但在[我们无法使用它。
借助此运算符,我们可以在字符串和扩展正则表达式之间进行比较。运算符右侧的字符串将被视为扩展正则表达式。 让我们创建两个使用此运算符处理正则表达式的示例:
$ [[ Blogdemo =~ .*e.* ]] && echo True || echo False
True
$ [[ Blogdemo2 =~ ^(b|B).*g$ ]] && echo True || echo False
False
总而言之,何时使用其中一个取决于我们想要实现的目标。
正如我们所见,** [[关键字比[和test内置函数有更多选项,但内置函数是 POSIX,因此,使用这些,我们可以避免失去可移植性。**
5. 关于空间的快速说明
正如我们所看到的,我们可以使用这些选项来评估参数,但请记住,我们需要遵循一种简单的语法,即在标记之间添加空格。
例如,这个例子可以正常工作:
[ -e file ]
但以下列表的每个元素都不会:
[ -e file]
[-e file]
[-efile]
让我们看看如果我们不使用空格分隔每个标记会发生什么:
$ [ 1=1] && echo True
bash: [: missing `]'
现在让我们尝试使用令牌*[[*:
$ [[ 1=1]] && echo True
bash: conditional binary operator expected
bash: syntax error near `True'
所以,我们在造句的时候一定要注意一下。