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="an@email.example" 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
if [ $STATUS -ne 200 ] -a [[ "$STRING" != "$VALUE" ]]
Could you please be more precise as to what «doesn’t work» ? Do you have a specific error message, or does is simply not provide the expected output ?
-a has duplicity. When used with the Bourne shell style test command, a.k.a. [ , the it means and . When used as a conditional expression then it is testing to see if a file exists. Yes it is confusing, best avoided.
5 Answers 5
What you have should work, unless $ is empty. It would probably be better to do:
if ! [ "$" -eq 200 ] 2> /dev/null && [ "$" != "$" ]; then
It’s hard to say, since you haven’t shown us exactly what is going wrong with your script.
Personal opinion: never use [[ . It suppresses important error messages and is not portable to different shells.
If STATUS is empty, the code from @HTF would have failed on -ne: unary operator expected . In your case, it will fail on integer expression expected , won’t it ?
I understand that. But you highlight the issue that $STATUS might be empty and suggest a solution (quoting it). Your solution still fails with an empty STATUS , that is all I meant.
@jvivenot You have a point. (My response to your comment was made before you edited your comment, when your comment merely read «the code . would have failed». A simple solution is to use $
Yes, but Google’s Shell Style Guide is misnamed, because it is a style guide for bash. If you care about portability (and you should), you cannot use [[
Further, consider the (lack of) error message generated by [[ in a=»this is not an integer»; test «$a» -ge 0; [[ $a -ge 0 ]]; IMO, the error message is useful, and [[ incorrectly suppresses it. Also, [[ returns 0, while test returns 1. IMO, this makes [[ unusable.
if [ $STATUS -ne 200 -a "$STRING" != "$VALUE" ]; then
Looks like it works better in some cases. The && operator does not look to work with shell substitution and with two &&. I would suggest trying this option first.
The «-a» operator also doesn’t work:
if [ $STATUS -ne 200 ] -a [[ «$STRING» != «$VALUE» ]]
For a more elaborate explanation: [ and ] are not Bash reserved words. The if keyword introduces a condition to be evaluated by a process 1 . The condition is true if the process’s exit status is 0 , or false otherwise).
For use as condition evaluation processes, there is the test program ( man test ), which is a program that lets you evaluate simple conditions, like file existance tests, string tests, and combinations thereof using e.g. -a and -o logical operator arguments.
As some find lines like if test -f filename; then foo bar; fi , etc. annoying, there is also a program called [ which is in fact a symlink to the test program. When test is called as [ , it expects to find ] as an additional terminating argument — this syntax is made up by the test program and bash is not aware of it at all.
So if test -f filename is basically the same (in terms of processes spawned) as if [ -f filename ] . In both cases the test program will be started, and both processes should behave identically.
Here’s your mistake: if [ $STATUS -ne 200 ] -a [[ «$STRING» != «$VALUE» ]] will parse to if + some process invocation. In other words the [ program will be run with arguments $STATUS , -ne , 200 , ] , -a , [[ , «$STRING» , != , «$VALUE» , ]] 2 . Now note that there is a ] somewhere in the middle of the argument list passed to [ , and also there is a ] missing at the end of the list. This is why the program will complain.
Also note, in constrast to the [ program, [[ is indeed a special, «reserved» word in the shell’s syntax (it’s a bash extension, not standardized in POSIX sh ). But it’s only recognized as a special word if it’s the first word in a command. In your example, the [[ occurs later in the command and so will be parsed as a normal argument, literally the string «[[» and be passed as part of the argument list to the invocation of the [ program.
1 Actually it need not be a single process but can be a job, a combination of processes connected by | , && , || etc..
2 Not literally these arguments — the shell will do variable substitution for $STATUS etc.
First, you don’t need to capitalize your variable names. In general, all-caps variable names should be reserved for ones that are being export ed into the environment for the benefit of other executables.
Second, what you have with && should work, but is not very resilient in the face of a status variable that might be empty or non-numeric.
Finally, the -a attempt didn’t work because -a is part of the syntax of test / [ . ] ; it goes inside the brackets.
Fundamentally, the most important thing to know about conditionals in the shell is that they are just commands. This if expression:
if foo; then echo true else echo false fi
Does exactly the same thing as this one:
foo if [ $? -eq 0 ]; then echo true else echo false fi
So the thing after the if (or while or until ) can be any command whatsoever.
That’s the main thing that a lot of beginning shell programmers miss. The shell doesn’t have expressions per se, just commands to run.
However, because you so often want to perform comparisons and other operations that would be Boolean expressions in regular non-shell programming languages, there is a special command that you can run to do those. It used to be a separate binary, but these days it’s built into the shell. Its real name is test , but it can also be invoked as [ , in which case it will complain if its last argument is not a matching ] . The arguments inside the brackets or after test constitute an expression to evaluate, and the test command exits with status 0 if the expression is true, and nonzero if it’s false. So it turns the sort of thing that is normally what an if statement expects in other programming languages into a command that you can run, effectively translating it for the shell.
The appearance of [ . ] can be misleading; it really is parsed just as if it were spelled test , with the same command-line processing as any regular shell command. That means that if you try to compare things using < or >they’ll be interpreted as redirects. Try to use ( and ) for grouping and you’ll be creating a subshell (and probably generating a syntax error by doing so in the middle of a command’s arguments). Try to combine expressions with && and || inside the brackets and you’ll be terminating the command early and again triggering a syntax error.
You can still use && and || outside of the brackets; at that point you’re just running multiple instances of the test command instead of just one. But you can also use -a and -o as long as they’re inside the brackets. So these two pipelines produce the same result:
[ "$status" -ne 200 -a "$string" != "$value" ] [ "$status" -ne 200 ] && [ "$string" != "value" ]
The difference is that the first one is all checked by one instance of the test command, while the second runs two of them.
The Korn Shell introduced a new version of the test command spelled with two brackets instead of one, between which lies a special syntactic environment that is not parsed the same as a regular command. Between the double brackets you can safely use unquoted parameter expansions, unquoted parentheses for grouping, < and >for string comparison (stick to -lt and -gt to compare numerically), and && and || as Boolean operators. [[ . ]] also adds the ability to match against glob patterns ( [[ foo == f* ]] ) and regular expressions ( [[ foo =~ ^f ]] ).
Modern shells such as Bash and Zsh have inherited this construct from Ksh, but it is not part of the POSIX specification. If you’re in an environment where you have to be strictly POSIX compliant, stay away from it; otherwise, it’s basically down to personal preference.
Note that the arithmetic substitution expression $(( . )) is part of POSIX, and it has very similar rules to [[ . ]] . The main difference is that equality and inequality tests (which here produce a numeric value, 0 for false and 1 for true) are done numerically instead of stringwise: [[ 10 < 2 ]] is true, but $(( 10 < 2 )) produces 0.
Here again, modern shells have expanded upon the POSIX spec to add a standalone $ -less version of (( . )) that functions as basically an autoquoted let command, which you can use in if expressions if you’re dealing with numbers. So your first condition could also be written (( status != 200 )) . Again, outside of having to stick to POSIX, that’s down to personal preference. I like using (( . )) whenever I’m dealing with numbers, but others prefer to just stick to [ or [[ and use the numeric operators like -lt .
So for what it’s worth, here’s how I’d rewrite your code:
website=domain.example subject="$website DOWN!" email=an@email.example 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"
How to represent multiple conditions in a shell if statement?
where line 15 is the one showing if . What is wrong with this condition? I guess something is wrong with the () .
You are not asking about shell conditions but test conditions. The whole expression in your example is evaluated by test ( [ ) and not by the shell. The shell evaluates just the exit status of [ .
8 Answers 8
Classic technique (escape metacharacters):
if [ \( "$g" -eq 1 -a "$c" = "123" \) -o \( "$g" -eq 2 -a "$c" = "456" \) ] then echo abc else echo efg fi
I've enclosed the references to $g in double quotes; that's good practice, in general. Strictly, the parentheses aren't needed because the precedence of -a and -o makes it correct even without them.
Note that the -a and -o operators are part of the POSIX specification for test , aka [ , mainly for backwards compatibility (since they were a part of test in 7th Edition UNIX, for example), but they are explicitly marked as 'obsolescent' by POSIX. Bash (see conditional expressions) seems to preempt the classic and POSIX meanings for -a and -o with its own alternative operators that take arguments.
With some care, you can use the more modern [[ operator, but be aware that the versions in Bash and Korn Shell (for example) need not be identical.
for g in 1 2 3 do for c in 123 456 789 do if [[ ( "$g" -eq 1 && "$c" = "123" ) || ( "$g" -eq 2 && "$c" = "456" ) ]] then echo "g = $g; c = $c; true" else echo "g = $g; c = $c; false" fi done done
Example run, using Bash 3.2.57 on Mac OS X:
g = 1; c = 123; true g = 1; c = 456; false g = 1; c = 789; false g = 2; c = 123; false g = 2; c = 456; true g = 2; c = 789; false g = 3; c = 123; false g = 3; c = 456; false g = 3; c = 789; false
You don't need to quote the variables in [[ as you do with [ because it is not a separate command in the same way that [ is.
I would have thought so. However, there is another alternative, namely:
if [ "$g" -eq 1 -a "$c" = "123" ] || [ "$g" -eq 2 -a "$c" = "456" ] then echo abc else echo efg fi
Indeed, if you read the 'portable shell' guidelines for the autoconf tool or related packages, this notation — using ' || ' and ' && ' — is what they recommend. I suppose you could even go so far as:
if [ "$g" -eq 1 ] && [ "$c" = "123" ] then echo abc elif [ "$g" -eq 2 ] && [ "$c" = "456" ] then echo abc else echo efg fi
Where the actions are as trivial as echoing, this isn't bad. When the action block to be repeated is multiple lines, the repetition is too painful and one of the earlier versions is preferable — or you need to wrap the actions into a function that is invoked in the different then blocks.