255

我正在尝试创建一个简单的 Bash 脚本来检查网站是否已关闭,并且由于某种原因“and”运算符不起作用:

#!/usr/bin/env bash

WEBSITE=domain.com
SUBJECT="$WEBSITE DOWN!"
EMAILID="an@email.com"
STATUS=$(curl -sI $WEBSITE | awk '/HTTP\/1.1/ { print $2 }')
STRING=$(curl -s $WEBSITE | grep -o "string_to_search")
VALUE="string_to_search"

if [ $STATUS -ne 200 ] && [[ "$STRING" != "$VALUE" ]]; then
    echo "Website: $WEBSITE is down, status code: '$STATUS' - $(date)" | mail -s "$SUBJECT" $EMAILID
fi

“-a”运算符也不起作用:

if [ $STATUS -ne 200 ] -a [[ "$STRING" != "$VALUE" ]]

能否请您告知何时使用:

  • 单方括号和双方括号
  • 插入语

?

4

5 回答 5

371

你所拥有的应该工作,除非${STATUS}是空的。这样做可能会更好:

if ! [ "${STATUS}" -eq 200 ] 2> /dev/null && [ "${STRING}" != "${VALUE}" ]; then

或者

if [ "${STATUS}" != 200 ] && [ "${STRING}" != "${VALUE}" ]; then

很难说,因为您没有向我们确切展示您的脚本出了什么问题。

个人意见:永远不要使用[[. 它抑制重要的错误消息,并且不能移植到不同的 shell。

于 2012-11-16T00:22:52.077 回答
66

试试这个:

if [ "${STATUS}" -ne 100 -a "${STRING}" = "${VALUE}" ]

或者

if [ "${STATUS}" -ne 100 ] && [ "${STRING}" = "${VALUE}" ]
于 2016-02-23T07:06:31.783 回答
33

试试这个:

if [ $STATUS -ne 200 -a "$STRING" != "$VALUE" ]; then
于 2012-11-16T00:25:11.163 回答
15

引用:

“-a”运算符也不起作用:

如果 [ $STATUS -ne 200 ] -a [[ "$STRING" != "$VALUE" ]]

更详细的解释:[and]不是 Bash 的保留字。关键字引入了要由作业评估的if条件(如果作业的返回值为真,则条件为真,0否则为假)。

对于琐碎的测试,有test程序 ( man test)。

正如一些发现行之类if test -f filename; then foo bar; fi的令人讨厌的行,在大多数系统上,您会找到一个名为的程序[,实际上它只是该test程序的符号链接。当test被称为 时[,您必须添加]为最后一个位置参数。

所以if test -f filename基本上与if [ -f filename ]. 在这两种情况下,test程序都将启动,并且两个进程的行为应该相同。

这是您的错误:if [ $STATUS -ne 200 ] -a [[ "$STRING" != "$VALUE" ]]将解析为if+ 一些工作,该工作是除if自身之外的一切。这项工作只是一个简单的命令(Bash 代表导致单个进程的东西),这意味着第一个单词 ( [) 是命令,其余是位置参数。在第一个之后还有剩余的参数]

也不是,[[确实是一个 Bash 关键字,但在这种情况下,它只被解析为一个普通的命令参数,因为它不在命令的前面。

于 2012-11-16T01:05:10.543 回答
1

首先,您不需要将变量名大写。通常,应该为那些被export编辑到环境中的变量名保留全大写变量名,以使其他可执行文件受益。

其次,你所拥有的&&应该可以工作,但在面对可能为空或非数字的状态变量时不是很有弹性。

最后,-a尝试没有成功,因为它是/ ...-a语法的一部分;它在括号内test[]

从根本上说,关于 shell 中的条件,最重要的是它们只是命令。这个 if 表达式:

if foo; then
  echo true
else
  echo false
fi

与此完全相同:

foo
if [ $? -eq 0 ]; then
  echo true
else
  echo false
fi

所以if(或whileuntil)之后的东西可以是任何命令。

对于&&and||运算符也是如此:两边的东西都只是命令。该序列foo && bar运行该命令foo,然后,只有当该命令以状态 0 退出时,它才会运行bar. (因此它实际上是 . 的缩写if foo; then bar; fi。)类似地,foo || bar运行foo,然后,如果该命令以非零状态退出,则运行bar. (所以它实际上是 . 的缩写if ! foo; then bar; fi。)

这是许多初学 shell 程序员错过的主要事情。shell 本身没有表达式,只有运行命令。

但是,由于您经常想要在常规非 shell 编程语言中执行比较和其他可能是布尔表达式的操作,因此您可以运行一个特殊的命令来执行这些操作。它曾经是一个单独的二进制文件,但现在它已内置到 shell 中。它的真名是test,但也可以调用 as [,在这种情况下,如果它的最后一个参数不匹配,它会报错]。括号内或之后的参数test构成要评估的表达式,如果表达式为真,则测试命令以状态 0 退出,如果为假,则以非零状态退出。因此,它将通常是语句在其他编程语言中所期望的那种东西if变成你可以运行的命令,有效地将它翻译成 shell。

[...的出现]可能会产生误导;它确实像拼写一样被解析test,使用与任何常规 shell 命令相同的命令行处理。这意味着如果您尝试比较事物,<否则>它们将被解释为重定向。尝试使用(and)进行分组,您将创建一个子shell(并且可能会在命令的参数中间生成语法错误)。尝试将表达式与&&括号||结合起来,您将提前终止命令并再次触发语法错误。

你仍然可以在括号&&使用and ;那时,您只是在运行 test 命令的多个实例,而不仅仅是一个。但是你也可以使用and只要它们在括号内。所以这两个管道产生相同的结果:|| -a-o

[ "$status" -ne 200 -a "$string" != "$value" ]

[ "$status" -ne 200 ] && [ "$string" != "value" ]

不同之处在于第一个由test命令的一个实例检查,而第二个运行其中两个。

Korn Shell 引入了一个新版本的测试命令,它用两个括号而不是一个括号拼写,在这两个括号之间是一个特殊的语法环境,它与常规命令的解析方式不同。在双括号之间,您可以安全地使用不带引号的参数扩展、不带引号的括号进行分组<>字符串比较(坚持-lt-gt以数字方式比较),&&以及||作为布尔运算符。[[...]]还增加了匹配 glob 模式 ( [[foo == f* ]]) 和正则表达式 ( [[ foo =~ ^f ]]) 的能力。

Bash 和 Zsh 等现代 shell 从 Ksh 继承了这种结构,但它不是 POSIX 规范的一部分。如果您处于必须严格遵守 POSIX 的环境中,请远离它;否则,这基本上取决于个人喜好。

请注意,算术替换表达式$((...)) POSIX 的一部分,它的规则与[[...非常相似]]。主要区别在于相等和不等式测试(这里产生一个数值,0 表示假,1 表示真)以数字方式而不是字符串方式完成:[[ 10 < 2 ]]为真,但 $(( 10 < 2 ))产生 0。

再一次,现代 shell 扩展了 POSIX 规范,添加了一个独立的$-less 版本的((... )),它的功能基本上是一个自动引用的命令,如果你正在处理数字let,你可以在表达式中使用它。if所以你的第一个条件也可以写成(( status != 200 ))。同样,除了必须坚持 POSIX 之外,这取决于个人喜好。我喜欢在处理数字时使用((... ,但其他人更喜欢坚持使用or并使用数字运算符,例如.))[[[-lt

因此,对于它的价值,这是我重写代码的方式:

website=domain.com
subject="$website DOWN!"
email=an@email.com
value="string_to_search"
status=$(curl -sI "$website" | awk '/HTTP\/1.1/ { print $2 }')
string=$(curl -s "$website" | grep -o "$value")

if (( status != 200 )) && [[ $string != $value ]]; then
    mail -s "$subject" "$email" <<<"Website: $website is down, status code: '$status' - $(date)"
fi
于 2022-02-17T23:00:47.770 回答