How to represent multiple conditions in a shell if statement?
Asked Answered
B

8

406

I want to represent multiple conditions like this:

if [ ( $g -eq 1 -a "$c" = "123" ) -o ( $g -eq 2 -a "$c" = "456" ) ]   
then  
    echo abc;  
else  
    echo efg;   
fi  

but when I execute the script, it shows

syntax error at line 15: `[' unexpected,

where line 15 is the one showing if ....

What is wrong with this condition? I guess something is wrong with the ().

Berte answered 29/9, 2010 at 22:41 Comment(3)
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 [.Espinoza
See also #16203588Sabir
Related: Compound if statements with multiple expressions in BashBoudicca
R
481

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.


Isn't it a classic question?

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.

Riboflavin answered 29/9, 2010 at 22:51 Comment(4)
It's good to know that escaped parentheses work; as an aside: in this particular case, the parentheses aren't even needed, because -a actually has higher precedence than -o (unlike && and || in the shell - however, inside bash [[ ... ]] conditionals, && also has higher precedence than || ). If you wanted to avoid -a and -o for maximum robustness and portability - which the POSIX man page itself suggests - you could also use subshells for grouping: if ([ $g -eq 1 ] && [ "$c" = "123" ]) || ([ $g -eq 2 ] && [ "$c" = "456" ])Lovieloving
Good solution, but I like the form without all the parenthesis and brackets: if test "$g" -eq 1 -a "$c" = "123" || test "$g" -eq 2 -a "$c" = "456"; then ...Vespasian
I'm a bit late to this, but it's still a top search result, so I'd just like to note that using && or || are also preferable as it behaves more like conditionals in other languages, and lets you short circuit conditions. For example: if [ -n "${check_inodes}" ] && [ "$(stat -f %i "/foo/bar")" = "$(stat -f %i "/foo/baz")" ]. Doing it this way, if check_inodes is empty, you avoid two calls to stat, whereas a larger, complex test condition must process all arguments before executing (which can also lead to bugs if you forget about that behaviour).Heteronomy
For anyone having further trouble making this work, ensure there are spaces between your conditions and the parentheses too.Aurelioaurelius
S
230

In Bash:

if [[ ( $g == 1 && $c == 123 ) || ( $g == 2 && $c == 456 ) ]]
Softhearted answered 29/9, 2010 at 23:46 Comment(6)
Definitely the best approach in bash. As an aside: in this particular case the parentheses aren't even needed, because inside [[ ... ]] conditionals && actually has higher precedence than || - unlike outside such conditionals.Lovieloving
Is there a way we can find out which condition matched here? Was it the one in first parenthesis or the other one?Conundrum
@Firelord: You'd need to separate the conditions into an if / else statement and have the code between then and fi put in a function in order to avoid repeating it. In very simple cases you could use a case statement with ;& fallthrough (in Bash 4).Softhearted
Whats the purpose of the two square brackets?Goldsberry
@peter: Please see my answer here, Conditional Constructs in the Bash Manual and BashFAQ/31.Softhearted
@DennisWilliamson thank You a lot, it helped me with diff-friendly formatting if statement conditions, moving each OR statement to separate lineGogol
E
54

Using /bin/bash the following will work:

if [ "$option" = "Y" ] || [ "$option" = "y" ]; then
    echo "Entered $option"
fi
Esquimau answered 21/1, 2015 at 10:23 Comment(0)
G
11

Be careful if you have spaces in your string variables and you check for existence. Be sure to quote them properly.

if [ ! "${somepath}" ] || [ ! "${otherstring}" ] || [ ! "${barstring}" ] ; then
Garget answered 15/11, 2013 at 17:40 Comment(1)
when we use the curly brackets after dollar symbol !!Albite
S
9

In Bash, you can use the following technique for string comparison

if [ $var OP "val" ]; then
    echo "statements"
fi

Example:

var="something"
if [ $var != "otherthing" ] && [ $var != "everything" ] && [ $var != "allthings" ]; then
    echo "this will be printed"
else
    echo "this will not be printed"
fi
Standin answered 14/10, 2019 at 8:31 Comment(0)
P
6
g=3
c=133
([ "$g$c" = "1123" ] || [ "$g$c" = "2456" ]) && echo "abc" || echo "efg"

Output:

efg

g=1
c=123
([ "$g$c" = "1123" ] || [ "$g$c" = "2456" ]) && echo "abc" || echo "efg"

Output:

abc

Pteridology answered 30/9, 2010 at 3:6 Comment(1)
That's a useless subshell. { ;} could be used instead.Inappropriate
R
0
#!/bin/bash

current_usage=$( df -h | grep 'gfsvg-gfslv' | awk {'print $5'} )
echo $current_usage
critical_usage=6%
warning_usage=3%

if [[ ${current_usage%?} -lt ${warning_usage%?} ]]; then
echo OK current usage is $current_usage
elif [[ ${current_usage%?} -ge ${warning_usage%?} ]] && [[ ${current_usage%?} -lt ${critical_usage%?} ]]; then
echo Warning $current_usage
else
echo Critical $current_usage
fi
Resound answered 18/5, 2017 at 11:43 Comment(1)
Welcome to Stack Overflow! This question is looking for an explanation, not simply for working code. Your answer provides no insight for the questioner, and may be deleted. Please edit to explain what causes the observed symptoms.Pro
S
-2

You can also chain more than two conditions:

if [ \( "$1" = '--usage' \) -o \( "$1" = '' \) -o \( "$1" = '--help' \) ]
then
   printf "\033[2J";printf "\033[0;0H"
   cat << EOF_PRINT_USAGE

   $0 - Purpose: upsert qto http json data to postgres db

   USAGE EXAMPLE:

   $0 -a foo -a bar



EOF_PRINT_USAGE
   exit 1
fi
Steeple answered 24/5, 2020 at 7:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.