Using case and arrays together in bash
Solution 1
With ksh93
, thanks to this bug, you can do:
IFS='|'
ARR=( opt1 opt2 opt3 )
IFS='|'
case $1 in
(@("${ARR[*]}"))
echo "Option is contained in the array";;
(*)
echo "Option is not contained in the array";;
esac
(I wouldn't rely on it as the bug might get fixed in the future).
With zsh
, you could do:
case ${ARR[(Ie)$1]}
(0)
echo "Option is not contained in the array";;
(*)
echo "Option is contained in the array";;
esac
(though, you'd probably rather want to use if ((ARR[(Ie)$1])); then echo is present...
here rather than a case construct).
${array[(I)pattern]}
returns the index of the last element that matches the pattern in the array, or 0 otherwise. The e
flag is for exact match (as opposed to pattern match).
With bash
, ksh
, yash
, zsh
, if you're ready to assume that $ARR
and $1
don't contain a certain character like @
, and that $ARR
won't be empty, you can do:
IFS=@
case "@${ARR[*]}@" in
(*"@$1@"*)
echo "Option is contained in the array";;
(*)
echo "Option is not contained in the array";;
esac
With bash -O extglob
, zsh -o kshglob -o globsubst
, you could define a helper that builds a pattern based on the elements of the array:
arraypat() {
awk '
BEGIN{
if (ARGC <= 1) print "!(*)"
else {
for (i = 1; i < ARGC; i++) {
gsub(/[][|<>\\?*()]/, "[&]", ARGV[i])
s = s sep ARGV[i]
sep = "|"
}
print "@(" s ")"
}
}' "$@"
}
case $1 in
($(arraypat "${ARR[@]}"))
echo "Option is contained in the array";;
(*)
echo "Option is not contained in the array";;
esac
Solution 2
Not really in a compact and easy to use way. Remember that $ARR
will expand to only the first element of the array, opt1
in your example.
You could use "${ARR[@]}"
, but using your data this would give a false positive for the string 1 opt
.
With more recent versions of bash
, you could consider using an associative array:
declare -A arr
arr=( [opt1]=1 [opt2]=1 [opt3]=1 )
if [[ "${arr[$1]}" -eq 1 ]]; then
# $1 is a key in arr
else
# is not
fi
Solution 3
Why would you want to do it with case
? It's meant for string pattern matching, not per-element matching.
Frankly, if you need the "contains" test often and want to make it short because of that, just put the hard part in a function instead of using ugly workarounds:
#!/bin/bash
ARR=( foo bar doo );
contains() {
typeset _x;
typeset -n _A="$1"
for _x in "${_A[@]}" ; do
[ "$_x" = "$2" ] && return 0
done
return 1
}
if contains ARR "$1" ; then
echo "\"$1\" is contained in ARR"
else
echo "\"$1\" not contained in ARR"
fi
(That should also work in ksh
)
Related videos on Youtube
Comments
-
red_trumpet almost 2 years
Is it possible to check if a variable is contained inside an array using
case
? I would like to do something likeARR=( opt1 opt2 opt3 ); case $1 in $ARR) echo "Option is contained in the array"; *) echo "Option is not contained in the array"; esac
-
Inian over 6 yearsAre you mandated to use
case
? -
muru over 6 yearsPossible duplicate of How do I test if an item is in a bash array?
-
Stéphane Chazelas over 6 years@muru, I'd say it's different here because of the
case
requirement. Presumably in the end the OP wants to add more cases as incase $1 in $ARR1)... $ARR2)... foo) bar)...; esac
-
-
ilkkachu over 6 yearsOr to get full use of the associative array, i.e. use arbitrary values, not just
1
, test with something like[[ "${arr[$1]+x}" ]]
(it's shorter, too!) -
Kusalananda over 6 years@ilkkachu Yes, or
[[ -n "${arr[$1]}" ]]
. -
RonJohn over 6 yearsOP probably wants a builtin for the presumed speed and efficiency.
-
ilkkachu over 6 years@RonJohn, everything that function does, is built in to Bash. As for speed, it's hard to say, we'd need to test, but for huge data sets, associative arrays would probably be faster (than either the loop, or a
case
), since key lookups are presumably O(1). But for huge data, you shouldn't use the shell anyway, and for small data, minor performance differences don't matter.