How to combine AND and OR condition in bash script for if condition?

13,542

Solution 1

The immediate problem with your statement is one of logic: you probably meant to write:

if [ "$#" -ne 1 ] || ! ([ "$1" = "ABC" ] || [ "$1" = "DEF" ] || [ "$1" = "GHI" ] || [ "$1" = "JKL" ]) 
then
  echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]" >&2
  exit 1
fi

That is: abort, if either more than 1 argument is given OR if the single argument given does NOT equal one of the acceptable values.

Note the ! to negate the expression in parentheses and the use of the POSIX-compliant form of the string equality operator, = (rather than ==).

However, given that you're using Bash, you can make do with a single [[ ... ]] conditional and Bash's regular-expression matching operator, =~:

if [[ $# -ne 1 || ! $1 =~ ^(ABC|DEF|GHI|JKL)$ ]] 
then
  echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]" >&2
  exit 1
fi

If POSIX compliance is not required, [[ ... ]] is preferable to [ ... ] for a variety of reasons. In the case at hand, $# and $1 didn't need quoting, and || could be used inside the conditional.

Note that =~ as used above works in Bash 3.2+, whereas the implicit extglob syntax used in anubhava's helpful answer requires Bash 4.1+;
in earlier versions you can, however, explicitly enable (and restore to its original value after) the extglob shell option: shopt -s extglob.

Solution 2

BASH actually allows use of extended glob inside [[ ... ]] and have && inside as well.

So you can do:

if [[ $# -ne 1 && $1 == @(ABC|DEF|GHI|JKL) ]]; then
   echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
   exit 1
fi

Solution 3

A few things:

  • [...] in bash is equivalent to the same test command (check the man page), so those && and || are not logical operators, but rather the shell equivalents
  • Parentheses in POSIX shell are not a grouping operator. They will work here, but they open a subshell, you are better off using standard test options of -a and -o (making your if statement if [ "$#" -ne 1 -a \( "$1" == "ABC" -o "$1" == "DEF" -o "$1" == "GHI" -o "$1" == "JKL" \) ], though based on your logic, it sounds like you actually want something like if [ "$#" -ne 1 -o \( "$1" != "ABC" -a "$1" != "DEF" -a "$1" != "GHI" -a "$1" != "JKL" \) ]. You probably can get better results with a case statement like follows:

usage() {
    echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
}


if [ "$#" -ne 1 ] 
then
    usage
    exit 1
fi

case "$1" in
    ABC)
        echo "Found ABC"
        ;;
    DEF)
        echo "Found DEF"
        ;;
    GHI)
        echo "Found GHI"
        ;;
    JKL)
        echo "Found JKL"
        ;;
    *)
        usage
        exit 1
        ;;
esac

If you want to pass a set of possible static arguments in, you might want to look at the getopts special shell command.

Share:
13,542
sijo0703
Author by

sijo0703

BY DAY: A humble state employee of TX BY NIGHT: Love to Learn Develop Administer software in various technologies and languages

Updated on July 28, 2022

Comments

  • sijo0703
    sijo0703 almost 2 years

    I was trying to combine logical AND & OR in a bash script within if condition. Somehow I am not getting the desired output and it is hard to troubleshoot. I am trying to validate the input parameters passed to a shell script for no parameter and the first parameter passed is valid or not.

    if [ "$#" -ne 1 ] && ([ "$1" == "ABC" ] || [ "$1" == "DEF" ] || [ "$1" == "GHI" ] || [ "$1" == "JKL" ]) 
    then
    echo "Usage: ./myscript.sh [ABC | DEF | GHI | JKL]"
    exit 1
    fi
    

    Can anyone point out what is going wrong here?

  • anubhava
    anubhava almost 8 years
    Yes that will also work but this one is shorter since it has only one [[ ... ]] expression
  • anubhava
    anubhava almost 8 years
    However I think in this code it should be $1 != ... but I just replicated OP's conditions
  • mklement0
    mklement0 almost 8 years
    ++, but please note that the implicit extglob syntax only works in Bash 4.1+.
  • mklement0
    mklement0 almost 8 years
    Good points; quibble: the POSIX spec. for test itself warns against use of -a, -o, and (): "The XSI extensions specifying the -a and -o binary primaries and the '(' and ')' operators have been marked obsolescent. (Many expressions using them are ambiguously defined by the grammar depending on the specific expressions being evaluated.) "
  • Taywee
    Taywee almost 8 years
    That's a good quibble. How would you alternately do this with just POSIX? command groups ({...}), &&, and ||?
  • chepner
    chepner almost 8 years
    Prior to 4.1, you just need to explicitly enable it with shopt -s extglob.
  • chepner
    chepner almost 8 years
    @Taywee: correct, you would use [ ... ] && { [ ... ] || [ ... ]; }
  • chepner
    chepner almost 8 years
    You can also shorten the case statement with ABC|DEF|GHI|JKL) echo "Found $1" ;;.
  • mklement0
    mklement0 almost 8 years
    Command groups are a good idea (to avoid a subshell); a pitfall worth pointing out: with test, -a has higher precedence than -o, but the shell's && and || have equal precedence.
  • Taywee
    Taywee almost 8 years
    @chepner I know, I'm operating under the assumption that they'll probably do different things and might want to be handled independently.