Is there an "in" operator in bash/bourne?
Solution 1
You can use case
... esac
$ cat in.sh
#!/bin/bash
case "$1" in
"cat"|"dog"|"mouse")
echo "dollar 1 is either a cat or a dog or a mouse"
;;
*)
echo "none of the above"
;;
esac
Ex.
$ ./in.sh dog
dollar 1 is either a cat or a dog or a mouse
$ ./in.sh hamster
none of the above
With ksh
, bash -O extglob
or zsh -o kshglob
, you could also use an extended glob pattern:
if [[ "$1" = @(cat|dog|mouse) ]]; then
echo "dollar 1 is either a cat or a dog or a mouse"
else
echo "none of the above"
fi
With bash
, ksh93
or zsh
, you could also use a regular expression comparison:
if [[ "$1" =~ ^(cat|dog|mouse)$ ]]; then
echo "dollar 1 is either a cat or a dog or a mouse"
else
echo "none of the above"
fi
Solution 2
There is not an "in" test in bash, but there is a regex test (not in bourne):
if [[ $1 =~ ^(cat|dog|mouse)$ ]]; then
echo "dollar 1 is either a cat or a dog or a mouse"
fi
And usually written using a variable (less problems with quoting):
regex='^(cat|dog|mouse)$'
if [[ $1 =~ $regex ]]; then
echo "dollar 1 is either a cat or a dog or a mouse"
fi
For an older Bourne shell you need to use a case match:
case $1 in
cat|dog|mouse) echo "dollar 1 is either a cat or a dog or a mouse";;
esac
Solution 3
Using a case
is fine and well if you have a fixed set of pets you want to match against. But it won't work if you need to build the pattern in runtime, as case
doesn't interpret alternation from within expanded parameters.
This will match only the literal string cat|dog|mouse
:
patt='cat|dog|mouse'
case $1 in
$patt) echo "$1 matches the case" ;;
esac
You can, however, use a variable with the regular expression match. As long as the variable isn't quoted, any regex operators within it have their special meanings.
patt='cat|dog|mouse'
if [[ "$1" =~ ^($patt)$ ]]; then
echo "$1 matches the pattern"
fi
You could also use associative arrays. Checking if a key exists in one is the closest thing to an in
operator that Bash gives. Though the syntax is a bit ugly:
declare -A arr
arr[cat]=1
arr[dog]=1
arr[mouse]=1
if [ "${arr[$1]+x}" ]; then
echo "$1 is in the array"
fi
(${arr[$1]+x}
expands to x
if arr[$1]
is set, empty otherwise.)
Solution 4
You could use a case
statement in an if
test, but the code would look a bit hairy:
if case "$1" in (cat|dog|mouse) true ;; (*) false; esac; then
printf '"%s" is one of cat, dog or mouse\n' "$1"
else
printf '"%s" is unknown\n' "$1"
fi
or slightly shorter,
if ! case "$1" in (cat|dog|mouse) false; esac; then
printf '"%s" is one of cat, dog or mouse\n' "$1"
else
printf '"%s" is unknown\n' "$1"
fi
This is using an case
clause just to do the pattern matching for the if
clause. It introduces an unnecessary true/false test.
It's better to just use case
:
case "$1" in
cat|dog|mouse)
printf '"%s" is one of cat, dog or mouse\n' "$1"
;;
*)
printf '"%s" is unknown\n' "$1"
esac
Don't do this:
is_one_of () {
eval "case $1 in ($2) return 0; esac"
return 1
}
if is_one_of "$1" 'cat|dog|mouse'; then
printf '"%s" is one of cat, dog or mouse\n' "$1"
else
printf '"%s" is unknown\n' "$1"
fi
or this:
is_one_of () (
word=$1
shift
IFS='|'
eval "case $word in ($*) return 0; esac"
return 1
)
if is_one_of "$1" cat dog mouse; then
printf '"%s" is one of cat, dog or mouse\n' "$1"
else
printf '"%s" is unknown\n' "$1"
fi
... because you're just adding more dangerous cruft, just to be able to use an if
statement in your code in place of a perfectly reasonable case
statement.
Solution 5
grep
approach.
if echo $1 | grep -qE "^(cat|dog|mouse)$"; then
echo "dollar 1 is either a cat or a dog or a mouse"
fi
-q
to avoid any output to screen (quicker to type than>/dev/null
).-E
for extended regular expressions(cat|dog|mouse)
aspects needs this.^(cat|dog|mouse)$
matches any lines starting (^
) with cat, dog or mouse ((cat|dog|mouse)
) followed by end of line ($
)
Related videos on Youtube
mrjayviper
Updated on September 18, 2022Comments
-
mrjayviper over 1 year
I’m looking for an “in” operator that works something like this:
if [ "$1" in ("cat","dog","mouse") ]; then echo "dollar 1 is either a cat or a dog or a mouse" fi
It's obviously a much shorter statement compared to, say, using several "or" tests.
-
mrjayviper almost 6 yearsany reason for the double brackets? Thanks again!
-
smw almost 6 years@mrjayviper the double brackets are an extended test construct - AFAIK the regex operator
=~
isn't valid inside the POSIX single-bracket test -
Sergiy Kolodyazhnyy almost 6 years@steeldriver Only
[
is POSIX, but[[
is extended feature of bash, ksh ( apparently it's originated from there, and zsh.case
example is most POSIX of all, though -
Sergiy Kolodyazhnyy almost 6 yearsWouldn't that be better to split case into function call and evaluate exit status inside the if statement?
-
Kusalananda almost 6 years@SergiyKolodyazhnyy And let the pattern be an argument to the function? It would have to do an
eval
of thecase
statement in that case, and it would be even more prone to errors. -
done almost 6 years@StéphaneChazelas Since at least bash 4.1-alpha there is no need to set extglog explicitly. From bash changes: s. Force extglob on temporarily when parsing the pattern argument to the == and != operators to the [[ command, for compatibility.
-
done almost 6 years@steeldriver The $1 in
case "$1" in
does not need to be quoted, no word splitting nor pathname expansion are performed in that token. -
smw almost 6 years@Cyrus thanks for the regex catch - StéphaneChazelas thanks for the improvements, the extended glob in particular