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?
The Bash logical (&&) operator is one of the most useful commands that can be used in multiple ways, like you can use in the conditional statement or execute multiple commands simultaneously.
The operators "&&" and "||" shall have equal precedence and shall be evaluated with left associativity. For example, both of the following commands write solely bar to standard output: $ false && echo foo || echo bar $ true || echo foo && echo bar.
"&&" is used to chain commands together, such that the next command is run if and only if the preceding command exited without errors (or, more accurately, exits with a return code of 0). "\" by itself at the end of a line is a means of concatenating lines together.
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
.
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
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-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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With