How to combine AND and OR condition in bash script for if condition?
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 sametest
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 statementif [ "$#" -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 likeif [ "$#" -ne 1 -o \( "$1" != "ABC" -a "$1" != "DEF" -a "$1" != "GHI" -a "$1" != "JKL" \) ]
. You probably can get better results with acase
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.
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, 2022Comments
-
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 almost 8 yearsYes that will also work but this one is shorter since it has only one
[[ ... ]]
expression -
anubhava almost 8 yearsHowever I think in this code it should be
$1 != ...
but I just replicated OP's conditions -
mklement0 almost 8 years++, but please note that the implicit
extglob
syntax only works in Bash 4.1+. -
mklement0 almost 8 yearsGood 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 almost 8 yearsThat's a good quibble. How would you alternately do this with just POSIX? command groups (
{...}
), &&, and ||? -
chepner almost 8 yearsPrior to 4.1, you just need to explicitly enable it with
shopt -s extglob
. -
chepner almost 8 years@Taywee: correct, you would use
[ ... ] && { [ ... ] || [ ... ]; }
-
chepner almost 8 yearsYou can also shorten the
case
statement withABC|DEF|GHI|JKL) echo "Found $1" ;;
. -
mklement0 almost 8 yearsCommand 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 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.