Is this the correct way to test for a number, with double [[]]
enclosing :digit:
and single quotes surrounding the regex ?
if [[ "$var" =~ '^[[:digit:]]+$' ]]; then
Is this the correct way to test for a number, with double [[]]
enclosing :digit:
and single quotes surrounding the regex ?
if [[ "$var" =~ '^[[:digit:]]+$' ]]; then
It is not, as a simple test will show us:
#!/bin/bash
for var in 1 2 3 a b c; do
if [[ "$var" =~ '^[[:digit:]]+$' ]]; then
echo "$var is a number"
else
echo "$var is NOT a number"
fi
done
Running the above produces:
1 is NOT a number
2 is NOT a number
3 is NOT a number
a is NOT a number
b is NOT a number
c is NOT a number
If you have an editor that runs shellcheck
automatically, or if you paste your code into https://shellcheck.net/, you'll see the following error:
Line 4:
if [[ "$var" =~ '^[[:digit:]]+$' ]]; then
^-- SC2076 (warning): Remove quotes from right-hand side of =~ to match as a regex rather than literally.
And that tells us we should instead write:
#!/bin/bash
for var in 1 2 3 a b c; do
if [[ "$var" =~ ^[[:digit:]]+$ ]]; then
echo "$var is a number"
else
echo "$var is NOT a number"
fi
done
Which produces:
1 is a number
2 is a number
3 is a number
a is NOT a number
b is NOT a number
c is NOT a number
This is documented in the bash man page:
An additional binary operator, =~, is available, with the same precedence as == and !=. When it is used, the string to the right of the operator is considered a POSIX extended regular expression and matched accordingly... If any part of the pattern is quoted, the quoted portion is matched literally. This means every character in the quoted portion matches itself, instead of having any special pattern matching meaning.
grep
.
– Vera
Jan 29 '23 at 20:31
Depends what you mean by number. 0x12
, inf
, 1.2
, -12
, ¹²³
, ⑱
, 1e6
, 1,000,000
, Ⅷ
, 0x123p-5
, 6,23
, 0b100110
are all numbers by some definitions of number, and 08
, 019
, 9999999999999999999999999999999999
are not valid numbers by some others.
To test whether a string is made of one or more ASCII decimal digits, you can use:
Standardly:
valid() {
case $1 in
("" | *[!0123456789]*) false;;
(*) true;;
esac
}
Or in ksh, bash (bash -O extglob
with older versions) or zsh -o kshglob
:
valid() [[ $1 = +([0123456789]) ]]
Or in bash / ksh93 / zsh / yash:
valid() [[ $1 =~ ^[0123456789]+$ ]]
valid() [[ $1 =~ '^[0123456789]+$' ]]
would also work in zsh¹ and bash 3.1 or bash -O compat31
. In zsh
, if the rematchpcre
option was enabled, you'd need to change it to [[ $1 =~ '^[0123456789]\z' ]]
as $
in PCRE matches at the end of the subject but also before a newline character at the end of the subject so would match on $'123\n'
for instance. You could also use [[ $1 =~ '^\d+\z' ]]
then.
In zsh
:
valid() [[ $1 = <-> ]]
In any case, avoid [[:digit:]]
which on some systems matches decimal digits other than the ASCII arabic ones. And except in zsh, and especially in bash, do not use [0-9]
for input validation as that often match random characters that happen to sort between 0 and 9.
¹ Note however that in zsh, variable values can contain the NUL byte and the standard regexp API (as opposed to PCRE's) doesn't support matching strings with NULs in them. When the rematchpcre
option is not enabled, [[ $1 =~ '^[0123456789]+$' ]]
would match on $'123\0xyz'
for instance.