Late to the show? Following very easy variant was not clearly mentioned yet. I use case
for checking simple lists, which is a general Bourne Shell idiom not relying on anything external nor extended:
haystack='a b c'
needle='b'
case " $haystack " in (*" $needle "*) :;; (*) false;; esac
- Please note the use of the separator (here:
SPC
) to correcyly delimit the pattern: At the beginning and end of " $haystack "
and likewise in the test of " $needle "
.
- This statement returns
true
($?=0
) in case $needle
is in $haystack
, false otherwise.
- Also you can test for more than one
$needle
very easily. When there are several similar cases like
if (haystack.contains(needle1)) { run1() } elif (haystack.contains(needle2)) { run2() } else { run3() }
you can wrap this into the case
, too:
case " $haystack " in (*" $needle1 "*) run1;; (*" $needle2 "*) run2;; (*) run3;; esac
and so on
This also works for all lists with values which do not include the separator itself, like comma:
haystack=' a , b , c '
needle=' b '
case ",$haystack," in (*",$needle,"*) :;; (*) false;; esac
Note that if values can contain anything including the separator sequence (except NUL
, as shells do not suport NUL
in variables as you cannot pass arguments containing NUL
to commands) then you need to use arrays. Arrays are ksh/bashisms and not supported by "ordinary" POSIX/Bourne shells. (You can work around this limitation using $@
in POSIX-Shells, but this is something completely different than what was aked here.)
Can the (*) false
part be left away?
- No, as this is the critical return value. By default
case
returns true
.
- Yes if you do not need the return value and put your processing at the location of the
:
Why the :;;
- We could also write
true;;
, but I am used to use :
instead of true
because it is shorter and faster to type
- Also I consider not writing anything bad practice, as it is not obvious to everybody that the default return value of
case
is true.
- Also "leaving out" the command usually indicates "something was forgotten here". So putting a redundant ":" there clearly indicates "it is intended to do nothing else than return true here".
In bash
you can also use ksh/bashisms like ;&
(fallthroug) or ;;&
(test other patterns) to express if (haystack.contains(needle1)) { run1(); }; if (haystack.contains(needle2)) { run2(); }
Hence usually case
is much more maintainable than other regex constructs. Also it does not use regex, it only use shell patterns, which might even be faster.
Reusable function:
: Needle "list" Seperator_opt
NeedleListSep()
{
if [ 3 -gt $# ];
then NeedleListSep "$1" "$2" " ";
else case "$3$2$3" in (*"$3$1$3"*) return 0;; esac; return 1;
fi;
}
In bash
you can simplify this to
: Needle "list" Seperator_opt
NeedleListSep()
{
local s="${3-" "}";
case "$s$2$s" in (*"$s$1$s"*) return 0;; esac; return 1;
}
Use like this
Test() {
NeedleListSep "$1" "a b c" && echo found $1 || echo no $1;
NeedleListSep "$1" "a,b,c" ',' && echo found $1 || echo no $1;
NeedleListSep "$1" "a # b # c" ' # ' && echo found $1 || echo no $1;
NeedleListSep "$1" "abc" '' && echo found $1 || echo no $1;
}
Test a
Test z
As shown above, this also works for degerated cases where the separator is the empty string (so each character of the list is a needle). Example:
Test
returns
no
no
no
found
As the empty string is cleary part of abc
in case your separator is the empty string, right?
Note that this function is Public Domain as there is absolutely nothing to it which can be genuinely copyrighted.