Contents

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 有关键字:ifthenelifelsefi

因此,例如,让我们使用 ifthenelsefi关键字:

$ 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'

所以,我们在造句的时候一定要注意一下。