Search code examples
bashif-statement

An "and" operator for an "if" statement in Bash


I'm trying to create a simple Bash script to check if the website is down and for some reason the "and" operator doesn't work:

#!/usr/bin/env bash

WEBSITE=domain.example
SUBJECT="$WEBSITE DOWN!"
EMAILID="[email protected]"
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

The "-a" operator also doesn't work:

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

Could you also please advise when to use:

  • single and double square brackets
  • parenthesis

?


Solution

  • If $STATUS expands to the empty string, then if [ $STATUS -ne 200 ] is invalid because it is equivalent to if [ -ne 200 ], which should generate an error similar to "-ne: unary operator expected".

    The command

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

    is invalid regardless of the expansion of $STATUS because it is passing [[ and ]] as arguments to the [ command, but [ requires that its last argument be the literal string ]. It would probably be better to do:

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

    or

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

    In each of these, ] is the final argument to the invocation of [, as required. The shell parses && and ; as tokens which terminate the [ command, but strings like -a, [[, and ]] are just strings that the shell passes to the command. (In the first case, the shell treats 2> /dev/null as a redirection operator, so those strings are not passed to [ as arguments). Note that using -a as an argument to [ as a boolean AND is considered bad practice, and ought to be avoided.

    [ and [[ are very different, but the only difference between [ and test is that [ requires that its final argument be ] and test does not. (Some will argue that another difference is that [ is a builtin but test is not, and although there may have been a time in the distant past when some shells had [ as a builtin while test was always an invocation of /bin/test, that probably has not been the case in any common shell for decades). Another reasonable solution is:

    if test "${STATUS}" != 200 && test "${STRING}" != "${VALUE}"; then ...
    

    Personal opinion: never use [[. It suppresses important error messages and is not portable to different shells. Also, as seen by the existence of this question, it is confusing. One may argue that the confusion here is caused by the shell's grammar and obscure behavior of [, and I would agree. [ is also confusing. You can prevent a lot of confusion if you avoid both [[ and [, and always use test.