As glenn says, “case
does not use regexes, it uses patterns”.
As bash(1) says,
case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac
A case
command first expands word
,
and tries to match it against each pattern
in turn,
using the same matching rules as for pathname expansion
(see Pathname Expansion below).
Similarly, the POSIX specification says,
… each pattern … shall be compared against the expansion of word,
according to the rules described in Pattern Matching Notation …
So the patterns are pathname expansion patterns,
a.k.a. wildcards, a.k.a. globs, as in ls -l -- *.sh
or rm -- *.bak
.
Sure, shopt -s extglob
and [[ … =~ … ]]
are the neatest thing since sliced bread,
but they aren’t POSIX,
and it can be useful to know how to use the original tools.
For years, programmers checked, for example,
whether a string was a number
by checking whether it was not not a number.
You’ve defined a number to be a string that consists
(entirely) of one or more digits.
So a string is not a number if it is null,
or if it contains a character that is not a digit.
We can test these conditions with a case
statement as follows:
case "$1" in
("")
# null
︙
;;
(*[!0-9]*)
# contains non-numeric character(s)
︙
;;
(*)
# is a whole number (non-negative integer)
︙
esac
where [!0-9]
is the old-timey shell way of saying [^0-9]
,
which, of course, means any character other than a digit.
([!…]
and [^…]
both work in bash.
[!…]
is required to work by POSIX; the result of [^…]
is unspecified.)
If you don’t care which kind of non-number a string is,
you can combine the non-number patterns:
case "$1" in
("" | *[!0-9]*)
# not a number
︙
;;
(*)
# is a number
︙
esac
As an exercise,
here’s a case
statement to handle any kind of real number —
to be precise, a string of one or more digits,
with optionally a period (.
) somewhere,
and optionally a minus sign (-
) at the beginning.
case "$1" in
(*[!-.0-9]*)
# contains non-numeric character(s)
;;
(*?-*)
# contains '-' somewhere other than the first position
;;
(*.*.*)
# contains multiple decimal points
;;
(*)
case "$1" in
(*[0-9]*)
# is a real number
;;
(*)
# not a number
esac
esac
I added the case
-within-a-case
to verify that the string does, indeed,
contain at least one digit.
That wasn’t necessary in the integer example
because I tested whether the string was null;
a test which I have removed from this statement.
Without the second case
, a single -
or a single .
—
or even -.
— would qualify as a number.
Of course we could add patterns to handle those exceptions,
but that can get complex.
(For example, I almost posted this answer
without realizing that -.
was one of the exceptions.)
I believe that the above approach is more flexible and robust.
Of course the non-number patterns can be combined here, too:
(*[!-.0-9]* | *?-* | *.*.*)
.
*
, at least thats what I thought. – siery Mar 21 '18 at 19:35${!i}
works fine for those. e.g.set -- aa bb cc; i=2; echo ${!i}
printsbb
– ilkkachu Mar 21 '18 at 22:45for val in "$@"; do ...
and use$val
in the loop – ilkkachu Mar 21 '18 at 22:46i
contains1
, so${!i}
is the same as$1
: it expands to the value of the first argument, be it64
orabc
or whatever. What they have is just a convoluted way of looping over the positional parameters / command line arguments. – ilkkachu Mar 21 '18 at 23:14for i do something with "$i"; done
– Stéphane Chazelas Mar 22 '18 at 07:17