-1

Is this the correct way to test for a number, with double [[]] enclosing :digit: and single quotes surrounding the regex ?

if [[ "$var" =~ '^[[:digit:]]+$' ]]; then
Kusalananda
  • 333,661
Vera
  • 1,223

2 Answers2

1

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.

larsks
  • 34,737
1

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.