71

I'm trying to check if an input is an integer and I've gone over it a hundred times but don't see the error in this. Alas it does not work, it triggers the if statement for all inputs (numbers/letters)

read scale
if ! [[ "$scale" =~ "^[0-9]+$" ]]
        then
            echo "Sorry integers only"
fi

I've played around with the quotes but either missed it or it did nothing. What do I do wrong? Is there an easier way to test if an input is just an INTEGER?

11 Answers11

69

Remove quotes

if ! [[ "$scale" =~ ^[0-9]+$ ]]
    then
        echo "Sorry integers only"
fi
jimmij
  • 47,140
38

Use -eq operator of test command:

read scale
if ! [ "$scale" -eq "$scale" ] 2> /dev/null
then
    echo "Sorry integers only"
fi

It not only works in bash but also any POSIX shell. From POSIX test documentation:

n1 -eq  n2
    True if the integers n1 and n2 are algebraically equal; otherwise, false.
cuonglm
  • 153,898
  • that checks if its any number, not just integers – lonewarrior556 Aug 22 '14 at 17:27
  • 5
    @lonewarrior556: It works only for integer, see: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html. I guess you said for any number because you use new test [[ instead of old test [ as mine. – cuonglm Aug 22 '14 at 17:27
  • 3
    Good idea but a bit noisy. I would rather not have to redirect errors to dev null. – Wildcard Oct 16 '16 at 08:09
  • 5
    @Wildcard: Yep, we pay it for portability. – cuonglm Oct 16 '16 at 08:13
  • A potential gotcha: this does not work if you have set the integer attribute on your variable beforehand (i.e. if the script above included declare -i scale before the read command). The problem occurs during assignment to an integer variable because any string will be interpreted as a variable name, and dereferenced (recursively). Unset or null "variable names" evaluate to 0. E.g. unset abc; declare -i a; a=abc; # a = 0 or a=3; b=a; c=b; declare -i d=c; # d=3. – SpinUp __ A Davis Mar 22 '24 at 17:49
20

As the OP seems to want only positive integers:

[ "$1" -ge 0 ] 2>/dev/null

Examples:

$ is_positive_int(){ [ "$1" -ge 0 ] 2>/dev/null && echo YES || echo no; }
$ is_positive_int word
no
$ is_positive_int 2.1
no
$ is_positive_int -3
no
$ is_positive_int 42
YES

Note that a single [ test is required:

$ [[ "word" -eq 0 ]] && echo word equals zero || echo nope
word equals zero
$ [ "word" -eq 0 ] && echo word equals zero || echo nope
-bash: [: word: integer expression expected
nope

This is because dereferencing occurs with [[:

$ word=other
$ other=3                                                                                                                                                                                  
$ [[ $word -eq 3 ]] && echo word equals other equals 3
word equals other equals 3
Tom Hale
  • 30,455
  • Note that in bash, it would return yes for " 123 " for instance. You wouldn't want to use that approach in ksh where it would not only work but also be a command injection vulnerability. – Stéphane Chazelas Jan 25 '21 at 10:50
  • The entire matter of testing with "[[" vs "[", and the subtle differences therein which can often be lost to memory after reading up on the matter after only a few months, is precisely demonstrated in this answer. Therefore, I consider any type of pattern that is not reliable in any type of shell to be a "bad". As a result, I tend to make all code within a script use only one or the other type of test (i.e. if using bash, every test should be "[[" and would be considered "bad" if ever using "[" anywhere. Likewise for bourne shell, you can only use "[" and this type of test would be OK there) – Jon Aug 10 '23 at 20:16
12

A POSIX and portable solution is:

read scale
if     [ -z "${scale##*[!0-9]*}" ]; 
then   echo "Sorry integers only"
fi
  • 3
    By far the best answer yet too many people don't use parameter expansion. This works on arrays too, incl $@: params=("${@##[!0-9]}") -- Keep all positional arguments, but only use numbers - anything else will be converted to null string which conveniently evaluates to 0 in arithmetic evaluations (my use case - function parameter is a list of integers, unset or null should default to 0 but caller sets variable name when unset). – Thomas Guyot-Sionnest Apr 22 '20 at 13:13
  • getting this to work for all integers, not just non-negative, is trickier because of edge cases like scale=1-2. how to handle such a case depends on the application. – SpinUp __ A Davis Mar 22 '24 at 14:50
9

For unsigned integers I use:

read -r scale
[ -z "${scale//[0-9]}" ] && [ -n "$scale" ] || echo "Sorry integers only"

Tests:

$ ./test.sh
7
$ ./test.sh
   777
$ ./test.sh
a
Sorry integers only
$ ./test.sh
""
Sorry integers only
$ ./test.sh

Sorry integers only
5
( scale=${scale##*[!0-9]*}
: ${scale:?input must be an integer}
) || exit

That does the check and outputs your error.

mikeserv
  • 58,310
  • OPTIND is good here, too. just saiyan. – mikeserv Oct 29 '18 at 11:06
  • 1
    By far the best answer yet too many people don't use parameter expansion. This works on arrays too, incl $@: params=("${@##*[!0-9]*}") -- Keep all positional arguments, but only use numbers - anything else will be converted to null string which conveniently evaluates to 0 in arithmetic evaluations (my use case - function parameter is a list of integers, unset or null should default to 0 but caller sets variable name when unset). – Thomas Guyot-Sionnest Apr 21 '20 at 22:10
3

POSIXly:

case $1 in
  ("" | *[!0123456789]*)
    echo >&2 Sorry, decimal integer only
    exit 1
esac

Do not use [0-9] which often matches a lot more than [0123456789] (especially with bash globs, YMMV for bash's [[ =~ ]] operator which uses the system regexps where [0-9] may or may not match more than 0123456789 depending on the system and locale).

More on that at what is the meaning of this shell script function

Note that you may also want to reject numbers like 08 or 09 which some tools (including bash arithmetic operators) reject as invalid octal numbers.

  • "Do not use [0-9] which often matches a lot more" -- in which shell? Why? Where did you see this behavior in the wild? – josch Jan 08 '23 at 09:46
  • 1
    @josch, have you followed the "more on that..." link? I've covered that on many Q&As and comments here and elsewhere. See also https://lists.gnu.org/archive/html/bug-bash/2021-02/msg00054.html for bash – Stéphane Chazelas Jan 08 '23 at 09:59
  • Thank you, that was a very educative read! I've adjusted my answer accordingly and will go through my own shell scripts fixing these instances. – josch Jan 08 '23 at 10:10
1

In bash, you can use the extended patterns inside [[...]]:

scale=4
[[ $scale == +([[:digit:]]) ]] || echo "Sorry integers only"
# ==> no output
scale=4.0
[[ $scale == +([[:digit:]]) ]] || echo "Sorry integers only"
# ==> Sorry integers only
glenn jackman
  • 85,964
0

For X greater than 0 I use

[ 0$X -gt 0 2>/dev/null ] && echo "is number" || echo "is string"

this gives no error if X is empty

  • 1
    That's not only wrong, but also a command injection vulnerability in bash and a few other shells. Try for instance with X='x -o -v xx[$(reboot)1] -o 1' in bash (gives true and reboots). – Stéphane Chazelas Jan 25 '21 at 10:45
0

Less convoluted

The negation is now in the regular expression instead of the test condition.

if [[ $scale =~ [^0-9] ]]
    then
        echo "Sorry integers only"
fi

Explanation:

  • =~ binary operator where the string to the right of the operator is considered an extended regular expression and matched accordingly (as in regex(3))
  • [^0-9] matches a single character not present in the list 0-9.
0

The following is POSIX compliant, so it will work in other shells than bash as well. Just checking -z "${scale##*[!0-9]*}" as suggested by another answer here does not take into account that strings starting with zero (except for zero itself) or empty strings are also not valid base-10 integers.

case "$scale" in *[!0123456789]*|0?*|"")
    echo "Sorry integers only";;
esac

We need to spell out all valid digits because using a range like 0-9 matches on any character (or possibly multi-character collation element) that sorts in between 0 and 9. Thanks to @stéphane-chazelas for this insight. More about that here: https://lists.gnu.org/archive/html/bug-bash/2021-02/msg00054.html

If negative integers are valid in your use case, you can use:

case "$scale" in *[!0123456789-]*|-|?*-*|0?*|-0|"")
    echo "Sorry integers only";;
esac
josch
  • 355