25

How do I check to see if a variable is a number, or contains a number, in UNIX shell?

random
  • 9,774
  • 10
  • 66
  • 83
  • 1
    The title asks if a variable is a number, the description asks if it contains a number. Which do you want? Also, when you say number d o you mean integer or should it handle decimals? – gpojd Nov 21 '08 at 19:35
  • Do you consider 3.1415 a number? – Jens May 15 '14 at 09:28

14 Answers14

22
if echo $var | egrep -q '^[0-9]+$'; then
    # $var is a number
else
    # $var is not a number
fi
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • I don't know that this is correct, this checks that it is an integer (not a number). I would change the regexp to '[0-9]' to satisfy the problem if the question is in the description ("contains"), and change it to handle non-integer numbers if it is the question in the title ("is"). – gpojd Nov 21 '08 at 19:33
  • How many thousand of cycles does the fork and pipe and egrep waste for such a simple operation that could be done in the shell? Hope this isn't needed in a tight loop... – Jens May 15 '14 at 09:31
  • @Jens: You're right, this answer is not as efficient as one that doesn't spawn a subprocess, though most of the time that won't make any practical difference. I've up-voted your better answer. – Adam Rosenfield May 15 '14 at 17:32
  • @AdamRosenfield Thanks, that's a noble gesture in the face of criticism. The world needs more of that attitude. – Jens May 19 '14 at 08:27
  • Don't forget negative numbers. Assuming we're only talking about integers (whole numbers), not floating point (real) numbers - i.e., numbers that work with test(1)'s numeric operators (-eq, etc.), we could tweak the regex method here to include negative whole numbers like so: `echo $var | egrep -q '^-*[[:digit:]]+$' && echo is num || echo not num` (only for base 10, of course). That even works for excessively negated but legit numbers like ----234. – Juan May 02 '15 at 15:39
19

Shell variables have no type, so the simplest way is to use the return type test command:

if [ $var -eq $var 2> /dev/null ]; then ...

(Or else parse it with a regexp)

Piotr Lesnicki
  • 9,442
  • 2
  • 28
  • 26
  • 2
    If var is empty, this gives a false positive. Maybe: `if [ -n "$var" -a $var -eq $var 2> /dev/null ]; then echo is num; else echo not a num; fi` – Juan May 02 '15 at 15:27
  • Also nice that this works for basic bourne/korn shells (bashisms not needed). One downside: in the test(1) I used (FreeBSD 9, Fedora 20), this fails when var is excessively negated (e.g., --234), although the arithmetic operations work (e.g., `echo $((--234 * 2))`). – Juan May 02 '15 at 15:48
  • This failed with the Android version of `sh` I tested it with. – FKEinternet Mar 24 '22 at 01:50
16

No forks, no pipes. Pure POSIX shell:

case $var in
   (*[!0-9]*|'') echo not a number;;
   (*)           echo a number;;
esac

(Assumes number := a string of digits). If you want to allow signed numbers with a single leading - or + as well, strip the optional sign like this:

case ${var#[-+]} in
   (*[!0-9]*|'') echo not a number;;
   (*)           echo a number;;
esac
Jens
  • 69,818
  • 15
  • 125
  • 179
  • Doesn't handle negative numbers (e.g., -123, --123). – Juan May 02 '15 at 17:00
  • @Juan: which is why I explicitly stated this assumption. – Jens May 02 '15 at 20:08
  • Fair enough. I just thought I'd make it clear on all the various solutions. On some others, it's fairly easy to support negative numbers. Trying to support negative numbers using this method (normal globbing) is tougher. – Juan May 03 '15 at 22:40
  • @Juan I've added an easy modification to allow signed numbers. Thanks for the challenge :-) – Jens Apr 22 '17 at 09:31
  • Nice addition. Still doesn't work for perverse but valid --123. Interesting that freebsd's /bin/sh < 10 does (`sh -c 'var=--123; echo $(($var))'`) handle that. But freebsd >= 10 does not. – Juan Apr 28 '17 at 20:36
  • Turns out the --123 not working in freebsd 10+ was intentional - to explicitly indicate that the -- operator is not supported. '- -123' and '-(-123)' work (but are still problematic for the above number detection code). – Juan May 01 '17 at 01:21
5

In either ksh93 or bash with the extglob option enabled:

if [[ $var == +([0-9]) ]]; then ...
Jens
  • 69,818
  • 15
  • 125
  • 179
Darron
  • 21,309
  • 5
  • 49
  • 53
  • To handle negative numbers (even excessively but legitimately negated like ---234), tweak that to `if [[ $var == +(-*[[:digit:]]) ]]; then echo is num; else echo not num; fi`. Nice that this handles empty or unset `var` as well as multi-word `var` (e.g., 'brown cow'). Use `[:xdigit:]` character class for base 16 nums. Note: locale independent character classes (e.g., [:digit:]) don't seem to work with ksh. – Juan May 02 '15 at 16:30
  • This gets complicated quickly. Try testing with 'brown cow', '123Q', '0xAbc', '123 Q', '-123', '-----345'. And the varied implementations of 'extended regular expressions' in bash/ksh contribute to the complexity. Downvote for lack of portability for the basic posix shells (more a downvote for the method rather than Darron's answer since he was very up front with that limitation). – Juan May 02 '15 at 17:33
  • can u guys help me to understand this: # x=200 # [[ +([0-9]) == $x ]] && echo number || echo not number > not a number # [[ $x == +([0-9]) ]] && echo number || echo not number > number – Thiago Conrado Jul 03 '20 at 18:43
  • @ThiagoConrado This is the same problem as expecting a=5 does the same as 5=a. The `==` operator is not symmetric in what it accepts on each side. – Jens Feb 28 '22 at 16:12
4

Here's a version using only the features available in a bare-bones shell (ie it'd work in sh), and with one less process than using grep:

if expr "$var" : '[0-9][0-9]*$'>/dev/null; then
    echo yes
else
    echo no
fi

This checks that the $var represents only an integer; adjust the regexp to taste, and note that the expr regexp argument is implicitly anchored at the beginning.

Norman Gray
  • 11,978
  • 2
  • 33
  • 56
2

This can be checked using regular expression.

 ###    
    echo $var|egrep '^[0-9]+$'
    if [ $? -eq 0 ]; then
        echo "$var is a number"
    else
        echo "$var is not a number"
    fi
aizaz
  • 3,056
  • 9
  • 25
  • 57
Wasim
  • 21
  • 1
2

I'm kind of newbee on shell programming so I try to find out most easy and readable It will just check the var is greater or same as 0 I think it's nice way to choose parameters... may be not what ever... :

if [ $var -ge 0 2>/dev/null ] ; then ...
1

INTEGER

if echo "$var" | egrep -q '^\-?[0-9]+$'; then 
    echo "$var is an integer"
else 
    echo "$var is not an integer"
fi

tests (with var=2 etc.):

2 is an integer
-2 is an integer
2.5 is not an integer 
2b is not an integer

NUMBER

if echo "$var" | egrep -q '^\-?[0-9]*\.?[0-9]+$'; then 
    echo "$var is a number"
else 
    echo "$var is not a number"
fi

tests (with var=2 etc.):

2 is a number
-2 is a number
-2.6 is a number
-2.c6 is not a number
2. is not a number
2.0 is a number
Oguz
  • 19
  • 4
  • Sure, .6 is a number. The command also shows it as a number. Also 4. is a number but this command tells its not a number. – Oguz Oct 06 '15 at 07:22
0
if echo $var | egrep -q '^[0-9]+$'

Actually this does not work if var is multiline.

ie

var="123
qwer"

Especially if var comes from a file :

var=`cat var.txt`

This is the simplest :

if [ "$var" -eq "$var" ] 2> /dev/null
then echo yes
else echo no
fi
Vouze
  • 1,678
  • 17
  • 10
  • Slight downvote for repetition of Piotr's 2008 answer without attribution. Same false positive problem as with that one (add `-n "$var"` to catch empty/unset var). See comments there. Good point on potential false positives for the multiline issue. – Juan May 02 '15 at 17:20
0

Here is the test without any regular expressions (tcsh code):

Create a file checknumber:

#! /usr/bin/env tcsh
if ( "$*" == "0" ) then
    exit 0 # number
else
    ((echo "$*" | bc) > /tmp/tmp.txt) >& /dev/null
    set tmp = `cat /tmp/tmp.txt`
    rm -f /tmp/tmp/txt
    if ( "$tmp" == "" || $tmp == 0 ) then
        exit 1 # not a number
    else
        exit 0 # number
    endif

endif

and run

chmod +x checknumber

Use

checknumber -3.45

and you'll got the result as errorlevel ($?).

You can optimise it easily.

Community
  • 1
  • 1
  • Fork heavy. Lighter implementations exist for whole numbers (see previous answers) unless all you have is csh. Slight upvote for handling floating point, although I am guessing that's not what the OP wanted (the OP was quite vague on the problem description). – Juan May 02 '15 at 17:27
0
( test ! -z "$num" && test "$num" -eq "$num" 2> /dev/null ) && {
   # $num is a number
}
cab404
  • 152
  • 1
  • 5
  • This will fail in unattractive ways because of the [lack of quoting](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) for any nontrivial string input. – tripleee Mar 21 '21 at 16:34
  • Is the `-z` check really necessary though? `test "" -eq "" 2>/dev/null` does what I expect here (old Bash 3.2 on standard macOS). Regardless, the parentheses seem superfluous (and cost you an unnecessary subshell). – tripleee Mar 25 '21 at 11:10
-1
a=123
if [ `echo $a | tr -d [:digit:] | wc -w` -eq 0 ]
then
    echo numeric
else
    echo ng
fi

numeric

a=12s3
if [ `echo $a | tr -d [:digit:] | wc -w` -eq 0 ]
then
    echo numeric
else
    echo ng
fi

ng

Елин Й.
  • 953
  • 10
  • 25
petie
  • 1
  • 2
    Could you add an explanation of your solution? – Steve Westbrook Oct 02 '13 at 15:28
  • @StevenWestbrook: this removes digits from $a and counts what's left over. If there is anything left, it's not a string of only digits ("not numeric"). – Juan May 02 '15 at 16:48
  • Doesn't handle negative numbers (e.g., -123, --123). Unfortunately, adding support for neg numbers using this fork-heavy method means adding another `tr -d -` in the pipeline I think. – Juan May 02 '15 at 16:49
  • You can alter the tr(1) expression to '[[:digit:]-]' to catch negative numbers (or use [:xdigit:] for base 16 numbers), but that gives a false positive with something like 123-54 (thinks that's a valid number). – Juan May 02 '15 at 17:15
-1

You can do that with simple test command.

$ test ab -eq 1 >/dev/null 2>&1
$ echo $?
2

$ test 21 -eq 1 >/dev/null 2>&1
$ echo $?
1

$ test 1 -eq 1 >/dev/null 2>&1
$ echo $?
0

So if the exit status is either 0 or 1 then it is a integer , but if the exis status is 2 then it is not a number.

Sriharsha Kalluru
  • 1,743
  • 3
  • 21
  • 27
  • Downvote - essentially the same as @Piotr's 2008 answer. Doesn't anyone review existing answers before replying - especially in the same thread (let alone other similar threads)? – Juan May 02 '15 at 17:22
-1

Taking the value from Command line and showing THE INPUT IS DECIMAL/NON-DECIMAL and NUMBER or not:

NUMBER=$1

            IsDecimal=`echo "$NUMBER" | grep "\."`

if [ -n "$IsDecimal" ]
then
            echo "$NUMBER is Decimal"
            var1=`echo "$NUMBER" | cut -d"." -f1`
            var2=`echo "$NUMBER" | cut -d"." -f2`

            Digit1=`echo "$var1" | egrep '^-[0-9]+$'`
            Digit2=`echo "$var1" | egrep '^[0-9]+$'`
            Digit3=`echo "$var2" | egrep '^[0-9]+$'`


            if [ -n "$Digit1" ] && [ -n "$Digit3" ]
            then
                echo "$NUMBER is a number"
            elif [ -n "$Digit2" ] && [ -n "$Digit3" ]
            then
                echo "$NUMBER is a number"

            else
                echo "$NUMBER is not a number"
            fi
else
            echo "$NUMBER is not Decimal"

            Digit1=`echo "$NUMBER" | egrep '^-[0-9]+$'`
            Digit2=`echo "$NUMBER" | egrep '^[0-9]+$'`

            if [ -n "$Digit1" ] || [ -n "$Digit2" ]; then
                echo "$NUMBER is a number"
            else
                echo "$NUMBER is not a number"
            fi
fi