How do I test if a variable is a number in Bash?

LinuxBashShell

Linux Problem Overview


I just can't figure out how do I make sure an argument passed to my script is a number or not.

All I want to do is something like this:

test *isnumber* $1 && VAR=$1 || echo "need a number"

Any help?

Linux Solutions


Solution 1 - Linux

One approach is to use a regular expression, like so:

re='^[0-9]+$'
if ! [[ $yournumber =~ $re ]] ; then
   echo "error: Not a number" >&2; exit 1
fi

If the value is not necessarily an integer, consider amending the regex appropriately; for instance:

^[0-9]+([.][0-9]+)?$

...or, to handle numbers with a sign:

^[+-]?[0-9]+([.][0-9]+)?$

Solution 2 - Linux

Without bashisms (works even in the System V sh),

case $string in
    ''|*[!0-9]*) echo bad ;;
    *) echo good ;;
esac

This rejects empty strings and strings containing non-digits, accepting everything else.

Negative or floating-point numbers need some additional work. An idea is to exclude - / . in the first "bad" pattern and add more "bad" patterns containing the inappropriate uses of them (?*-* / *.*.*)

Solution 3 - Linux

The following solution can also be used in basic shells such as Bourne without the need for regular expressions. Basically any numeric value evaluation operations using non-numbers will result in an error which will be implicitly considered as false in shell:

"$var" -eq "$var"

as in:

#!/bin/bash

var=a

if [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null; then
  echo number
else
  echo not a number
fi

You can can also test for $? the return code of the operation which is more explicit:

[ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null
if [ $? -ne 0 ]; then
   echo $var is not number
fi

Redirection of standard error is there to hide the "integer expression expected" message that bash prints out in case we do not have a number.

CAVEATS (thanks to the comments below):

  • Numbers with decimal points are not identified as valid "numbers"
  • Using [[ ]] instead of [ ] will always evaluate to true
  • Most non-Bash shells will always evaluate this expression as true
  • The behavior in Bash is undocumented and may therefore change without warning
  • If the value includes spaces after the number (e.g. "1 a") produces error, like bash: [[: 1 a: syntax error in expression (error token is "a")
  • If the value is the same as var-name (e.g. i="i"), produces error, like bash: [[: i: expression recursion level exceeded (error token is "i")

Solution 4 - Linux

Nobody suggested bash's extended pattern matching:

[[ $1 == ?(-)+([0-9]) ]] && echo "$1 is an integer"

or using new POSIX style:

[[ $1 == ?(-)+([:digit:]) ]] && echo "$1 is an integer"

Solution 5 - Linux

This tests if a number is a non-negative integer. It is shell independent (i.e. without bashisms) and uses only shell built-ins:

[ ! -z "${num##*[!0-9]*}" ] && echo "is a number" || echo "is not a number";

A previous version of this answer proposed:

[ -z "${num##[0-9]*}" ] && echo "is a number" || echo "is not a number";

but this is INCORRECT since it accepts any string starting with a digit, as jilles suggested.

Solution 6 - Linux

(2nd) Full rewrite of this answer: Jun 2021 27.

Some performance and compatibility hints

There are some strongly different methods regarding different kinds of tests.

I reviewed most relevant methods and built this comparison.

Unsigned Integer is_uint()

These functions implement code to assess whether an expression is an unsigned integer, i.e. consists entirely of digits.

  • Using parameter expansion

    (This was my approach before all this!)

    isuint_Parm() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}
    
  • Using fork to grep

    isuint_Grep() { grep -qE '^[0-9]+$' <<<"$1"; }
    

    I test this method only once because it's very slow. This is just there to show what not to do.

  • Using [tag:bash] integer capabilities

    isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
    
  • Using case

    isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
    
  • Using [tag:bash]'s regex

    isuint_Regx() { [[ $1 =~ ^[0-9]+$ ]] ;}
    

Signed integer is_int()

These functions implement code to assess whether an expression is a signed integer, i.e. as above but permitting an optional sign before the number.

  • Using parameter expansion

    isint_Parm() { local chk=${1#[+-]}; [ "$chk" ] && [ -z "${chk//[0-9]}" ] ;}
    
  • Using [tag:bash] integer capabilities

    isint_Bash() { (( 10#$1 )) 2>/dev/null ;}
    
  • Using case

    isint_Case() { case ${1#[-+]} in ''|*[!0-9]*) return 1;;esac;}
    
  • Using [tag:bash]'s regex

    isint_Regx() { [[ $1 =~ ^[+-]?[0-9]+$ ]] ;}
    

Number (unsigned float) is_num()

These functions implement code to assess whether an expression is a floating-point number, i.e. as above but permitting an optional decimal point and additional digits after it. This does not attempt to cover numeric expressions in scientific notation (e.g. 1.0234E-12).

  • Using parameter expansion

    isnum_Parm() { local ck=${1#[+-]};ck=${ck/.};[ "$ck" ]&&[ -z "${ck//[0-9]}" ];}
    
  • Using [tag:bash]'s regex

    isnum_Regx() { [[ $1 =~ ^[+-]?([0-9]+([.][0-9]*)?|\.[0-9]+)$ ]] ;}
    
  • Using case

    isnum_Case() { case ${1#[-+]} in ''|.|*[!0-9.]*|*.*.*) return 1;; esac ;}
    

Tests of concepts

(You could copy/paste this test code after previous declared functions.)

testcases=(
    1 42 -3 +42 +3. .9 3.14 +3.141 -31.4 '' . 3-3 3.1.4 3a a3 blah 'Good day!'
);printf '%-12s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s\n' Function \
       U{Prm,Grp,Bsh,Cse,Rgx} I{Prm,Bsh,Cse,Rgx} N{Prm,Cse,Rgx}; \
for var in "${testcases[@]}";do
    outstr='';
    for func in isuint_{Parm,Grep,Bash,Case,Regx} isint_{Parm,Bash,Case,Regx} \
                       isnum_{Parm,Case,Regx};do
        if $func "$var"
        then outstr+='  num'
        else outstr+='  str'
        fi
    done
    printf '%-11s %s\n' "|$var|" "$outstr"
done

Should output:

Function     UPrm UGrp UBsh UCse URgx IPrm IBsh ICse IRgx NPrm NCse NRgx
|1|           num  num  num  num  num  num  num  num  num  num  num  num
|42|          num  num  num  num  num  num  num  num  num  num  num  num
|-3|          str  str  str  str  str  num  num  num  num  num  num  num
|+42|         str  str  num  str  str  num  num  num  num  num  num  num
|+3.|         str  str  str  str  str  str  str  str  str  num  num  num
|.9|          str  str  str  str  str  str  str  str  str  num  num  num
|3.14|        str  str  str  str  str  str  str  str  str  num  num  num
|+3.141|      str  str  str  str  str  str  str  str  str  num  num  num
|-31.4|       str  str  str  str  str  str  str  str  str  num  num  num
||            str  str  num  str  str  str  str  str  str  str  str  str
|.|           str  str  str  str  str  str  str  str  str  str  str  str
|3-3|         str  str  num  str  str  str  str  str  str  str  str  str
|3.1.4|       str  str  str  str  str  str  str  str  str  str  str  str
|3a|          str  str  str  str  str  str  str  str  str  str  str  str
|a3|          str  str  str  str  str  str  str  str  str  str  str  str
|blah|        str  str  str  str  str  str  str  str  str  str  str  str
|Good day!|   str  str  str  str  str  str  str  str  str  str  str  str

I hope! (Note: uint_bash seem not perfect!)

Performance comparison

Then I've built this test function:

testFunc() {
    local tests=1000 start=${EPOCHREALTIME//.}
    for ((;tests--;)) ;do
        "$1" "$3"
    done
    printf -v "$2" %u $((${EPOCHREALTIME//.}-start))
}
percent(){ local p=00$((${1}00000/$2));printf -v "$3" %.2f%% ${p::-3}.${p: -3};}
sortedTests() {
    local func NaNTime NumTime ftyp="$1" nTest="$2" tTest="$3" min i pct line
    local -a order=()
    shift 3
    for func ;do
        testFunc "${ftyp}_$func" NaNTime "$tTest"
        testFunc "${ftyp}_$func" NumTime "$nTest"
        order[NaNTime+NumTime]=${ftyp}_$func\ $NumTime\ $NaNTime
    done
    printf '%-12s %11s %11s %14s\n' Function Number NaN Total
    min="${!order[*]}" min=${min%% *}
    for i in "${!order[@]}";do
        read -ra line <<<"${order[i]}"
        percent "$i" "$min" pct
        printf '%-12s %9d\U00B5s %9d\U00B5s  %12d\U00B5s  %9s\n' \
               "${line[@]}" "$i" "$pct"
    done
}

I could run in this way:

sortedTests isuint "This is not a number." 31415926535897932384 \
            Case Grep Parm Bash Regx ;\
sortedTests isint  "This is not a number." 31415926535897932384 \
            Case Parm Bash Regx ;\
sortedTests isnum "This string is clearly not a number..." \
            3.141592653589793238462643383279502884  Case Parm Regx

On my host, this shows somthing like:

Function             Number            NaN            Total        Rank
isuint_Case         8,080µs        6,848µs         14,928µs     100.00%
isuint_Parm        10,571µs       13,061µs         23,632µs     158.31%
isuint_Regx        12,865µs       15,407µs         28,272µs     189.39%
isuint_Bash        19,054µs       17,182µs         36,236µs     242.74%
isuint_Grep     1,333,786µs    1,416,626µs      2,750,412µs   18424.52%

Function             Number            NaN            Total        Rank
isint_Case          8,860µs        7,813µs         16,673µs     100.00%
isint_Parm         14,141µs       16,774µs         30,915µs     185.42%
isint_Regx         14,202µs       17,375µs         31,577µs     189.39%
isint_Bash         18,988µs       16,598µs         35,586µs     213.43%

Function             Number            NaN            Total        Rank
isnum_Case          8,935µs        9,232µs         18,167µs     100.00%
isnum_Parm         18,898µs       22,577µs         41,475µs     228.30%
isnum_Regx         25,336µs       42,825µs         68,161µs     375.19%

You could download full isnum comparission script here or full isnum comparission script as text here., (with UTF8 and LATIN handling).

Conclusion

  • case way is clearly the quickest! About 3x quicker than regex and 2x quicker than using parameter expansion.
  • forks (to grep or any binaries) are to be avoided when not needed.

case method has become my favored choice:

is_uint() { case $1        in '' | *[!0-9]*              ) return 1;; esac ;}
is_int()  { case ${1#[-+]} in '' | *[!0-9]*              ) return 1;; esac ;}
is_unum() { case $1        in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}
is_num()  { case ${1#[-+]} in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}

About compatibility

For this, I wrote a little test script based on previous tests, with:

for shell in bash dash 'busybox sh' ksh zsh "$@";do
    printf "%-12s  " "${shell%% *}"
    $shell < <(testScript) 2>&1 | xargs
done

This shows:

bash          Success
dash          Success
busybox       Success
ksh           Success
zsh           Success

As I know other [tag:bash] based solution like regex and [tag:bash]'s integer won't work in many other shells and forks are resource expensive, I would prefer the case way (just before parameter expansion which is mostly compatible too).

Solution 7 - Linux

I'm surprised at the solutions directly parsing number formats in shell. shell is not well suited to this, being a DSL for controlling files and processes. There are ample number parsers a little lower down, for example:

isdecimal() {
  # filter octal/hex/ord()
  num=$(printf '%s' "$1" | sed "s/^0*\([1-9]\)/\1/; s/'/^/")

  test "$num" && printf '%f' "$num" >/dev/null 2>&1
}

Change '%f' to whatever particular format you require.

Solution 8 - Linux

I was looking at the answers and... realized that nobody thought about FLOAT numbers (with dot)!

Using grep is great too.
-E means extended regexp
-q means quiet (doesn't echo)
-qE is the combination of both.

To test directly in the command line:

$ echo "32" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is: 32

$ echo "3a2" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is empty (false)

$ echo ".5" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer .5

$ echo "3.2" | grep -E ^\-?[0-9]?\.?[0-9]+$  
# answer is 3.2

Using in a bash script:

check=`echo "$1" | grep -E ^\-?[0-9]*\.?[0-9]+$`

if [ "$check" != '' ]; then    
  # it IS numeric
  echo "Yeap!"
else
  # it is NOT numeric.
  echo "nooop"
fi

To match JUST integers, use this:

# change check line to:
check=`echo "$1" | grep -E ^\-?[0-9]+$`

Solution 9 - Linux

Just a follow up to @mary. But because I don't have enough rep, couldn't post this as a comment to that post. Anyways, here is what I used:

isnum() { awk -v a="$1" 'BEGIN {print (a == a + 0)}'; }

The function will return "1" if the argument is a number, otherwise will return "0". This works for integers as well as floats. Usage is something like:

n=-2.05e+07
res=`isnum "$n"`
if [ "$res" == "1" ]; then
     echo "$n is a number"
else
     echo "$n is not a number"
fi

Solution 10 - Linux

test -z "${i//[0-9]}" && echo digits || echo no no no

${i//[0-9]} replaces any digit in the value of $i with an empty string, see man -P 'less +/parameter\/' bash. -z checks if resulting string has zero length.

if you also want to exclude the case when $i is empty, you could use one of these constructions:

test -n "$i" && test -z "${i//[0-9]}" && echo digits || echo not a number
[[ -n "$i" && -z "${i//[0-9]}" ]] && echo digits || echo not a number

Solution 11 - Linux

This can be achieved by using grep to see if the variable in question matches an extended regular expression.

Test integer 1120:
yournumber=1120
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Output: Valid number.

Test non-integer 1120a:
yournumber=1120a
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Output: Error: not a number.


Explanation

  • The grep, the -E switch allows us to use extended regular expression '^[0-9]+$'. This regular expression means the variable should only [] contain the numbers 0-9 zero through nine from the ^ beginning to the $ end of the variable and should have at least + one character.
  • The grep, the -q quiet switch turns off any output whether or not it finds anything.
  • if checks the exit status of grep. Exit status 0 means success and anything greater means an error. The grep command has an exit status of 0 if it finds a match and 1 when it doesn't;

So putting it all together, in the if test, we echo the variable $yournumber and | pipe it to grep which with the -q switch silently matches the -E extended regular expression '^[0-9]+$' expression. The exit status of grep will be 0 if grep successfully found a match and 1 if it didn't. If succeeded to match, we echo "Valid number.". If it failed to match, we echo "Error: not a number.".


For Floats or Doubles

We can just change the regular expression from '^[0-9]+$' to '^[0-9]*\.?[0-9]+$' for floats or doubles.

Test float 1120.01:
yournumber=1120.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Output: Valid number.

Test float 11.20.01:
yournumber=11.20.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

Output: Error: not a number.


For Negatives

To allow negative integers, just change the regular expression from '^[0-9]+$' to '^\-?[0-9]+$'.

To allow negative floats or doubles, just change the regular expression from '^[0-9]*\.?[0-9]+$' to '^\-?[0-9]*\.?[0-9]+$'.

Solution 12 - Linux

For my problem, I only needed to ensure that a user doesn't accidentally enter some text thus I tried to keep it simple and readable

isNumber() {
    (( $1 )) 2>/dev/null
}

According to the man page this pretty much does what I want > If the value of the expression is non-zero, the return status is 0

To prevent nasty error messages for strings that "might be numbers" I ignore the error output

$ (( 2s ))
bash: ((: 2s: value too great for base (error token is "2s")

Solution 13 - Linux

Old question, but I just wanted to tack on my solution. This one doesn't require any strange shell tricks, or rely on something that hasn't been around forever.

if [ -n "$(printf '%s\n' "$var" | sed 's/[0-9]//g')" ]; then
    echo 'is not numeric'
else
    echo 'is numeric'
fi

Basically it just removes all digits from the input, and if you're left with a non-zero-length string then it wasn't a number.

Solution 14 - Linux

[[ $1 =~ ^-?[0-9]+$ ]] && echo "number"

Don't forget - to include negative numbers!

Solution 15 - Linux

I would try this:

printf "%g" "$var" &> /dev/null
if [[ $? == 0 ]] ; then
    echo "$var is a number."
else
    echo "$var is not a number."
fi

Note: this recognizes nan and inf as number.

Solution 16 - Linux

A clear answer has already been given by @charles Dufy and others. A pure bash solution would be using the following :

string="-12,345"
if [[ "$string" =~ ^-?[0-9]+[.,]?[0-9]*$ ]]
then
    echo $string is a number
else
    echo $string is not a number
fi

Although for real numbers it is not mandatory to have a number before the radix point.

To provide a more thorough support of floating numbers and scientific notation (many programs in C/Fortran or else will export float this way), a useful addition to this line would be the following :

string="1.2345E-67"
if [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]?-?[0-9]+$ ]]
then
    echo $string is a number
else
    echo $string is not a number
fi

Thus leading to a way to differentiate types of number, if you are looking for any specific type :

string="-12,345"
if [[ "$string" =~ ^-?[0-9]+$ ]]
then
    echo $string is an integer
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*$ ]]
then
    echo $string is a float
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]-?[0-9]+$ ]]
then
    echo $string is a scientific number
else
    echo $string is not a number
fi

Note: We could list the syntactical requirements for decimal and scientific notation, one being to allow comma as radix point, as well as ".". We would then assert that there must be only one such radix point. There can be two +/- signs in an [Ee] float. I have learned a few more rules from Aulu's work, and tested against bad strings such as '' '-' '-E-1' '0-0'. Here are my regex/substring/expr tools that seem to be holding up:

parse_num() {
 local r=`expr "$1" : '.*\([.,]\)' 2>/dev/null | tr -d '\n'` 
 nat='^[+-]?[0-9]+[.,]?$' \
 dot="${1%[.,]*}${r}${1##*[.,]}" \
 float='^[\+\-]?([.,0-9]+[Ee]?[-+]?|)[0-9]+$'
 [[ "$1" == $dot ]] && [[ "$1" =~ $float ]] || [[ "$1" =~ $nat ]]
} # usage: parse_num -123.456

Solution 17 - Linux

Can't comment yet so I'll add my own answer, which is an extension to glenn jackman's answer using bash pattern matching.

My original need was to identify numbers and distinguish integers and floats. The function definitions deducted to:

function isInteger() {
    [[ ${1} == ?(-)+([0-9]) ]]
}

function isFloat() {
    [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}

I used unit testing (with shUnit2) to validate my patterns worked as intended:

oneTimeSetUp() {
    int_values="0 123 -0 -123"
    float_values="0.0 0. .0 -0.0 -0. -.0 \
        123.456 123. .456 -123.456 -123. -.456
        123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
        123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
        123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
}

testIsIntegerIsFloat() {
    local value
    for value in ${int_values}
    do
        assertTrue "${value} should be tested as integer" "isInteger ${value}"
        assertFalse "${value} should not be tested as float" "isFloat ${value}"
    done
    
    for value in ${float_values}
    do
        assertTrue "${value} should be tested as float" "isFloat ${value}"
        assertFalse "${value} should not be tested as integer" "isInteger ${value}"
    done
    
}

Notes: The isFloat pattern can be modified to be more tolerant about decimal point (@(.,)) and the E symbol (@(Ee)). My unit tests test only values that are either integer or float, but not any invalid input.

Solution 18 - Linux

I use expr. It returns a non-zero if you try to add a zero to a non-numeric value:

if expr -- "$number" + 0 > /dev/null 2>&1
then
    echo "$number is a number"
else
    echo "$number isn't a number"
fi

It might be possible to use bc if you need non-integers, but I don't believe bc has quite the same behavior. Adding zero to a non-number gets you zero and it returns a value of zero too. Maybe you can combine bc and expr. Use bc to add zero to $number. If the answer is 0, then try expr to verify that $number isn't zero.

Solution 19 - Linux

One simple way is to check whether it contains non-digit characters. You replace all digit characters with nothing and check for length. If there's length it's not a number.

if [[ ! -n ${input//[0-9]/} ]]; then
    echo "Input Is A Number"
fi

Solution 20 - Linux

As i had to tamper with this lately and like karttu's appoach with the unit test the most. I revised the code and added some other solutions too, try it out yourself to see the results:

#!/bin/bash

	# N={0,1,2,3,...} by syntaxerror
function isNaturalNumber()
{
 [[ ${1} =~ ^[0-9]+$ ]]
}
	# Z={...,-2,-1,0,1,2,...} by karttu
function isInteger() 
{
 [[ ${1} == ?(-)+([0-9]) ]]
}
	# Q={...,-½,-¼,0.0,¼,½,...} by karttu
function isFloat() 
{
 [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}
	# R={...,-1,-½,-¼,0.E+n,¼,½,1,...}
function isNumber()
{
 isNaturalNumber $1 || isInteger $1 || isFloat $1
}

bools=("TRUE" "FALSE")
int_values="0 123 -0 -123"
float_values="0.0 0. .0 -0.0 -0. -.0 \
    123.456 123. .456 -123.456 -123. -.456 \
    123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
    123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
    123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
false_values="blah meh mooh blah5 67mooh a123bc"

for value in ${int_values} ${float_values} ${false_values}
do
	printf "  %5s=%-30s" $(isNaturalNumber $value) ${bools[$?]} $(printf "isNaturalNumber(%s)" $value)
	printf "%5s=%-24s" $(isInteger $value) ${bools[$?]} $(printf "isInteger(%s)" $value)
	printf "%5s=%-24s" $(isFloat $value) ${bools[$?]} $(printf "isFloat(%s)" $value)
	printf "%5s=%-24s\n" $(isNumber $value) ${bools[$?]} $(printf "isNumber(%s)" $value)
done

So isNumber() includes dashes, commas and exponential notation and therefore returns TRUE on integers & floats where on the other hand isFloat() returns FALSE on integer values and isInteger() likewise returns FALSE on floats. For your convenience all as one liners:

isNaturalNumber() { [[ ${1} =~ ^[0-9]+$ ]]; }
isInteger() { [[ ${1} == ?(-)+([0-9]) ]]; }
isFloat() { [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]; }
isNumber() { isNaturalNumber $1 || isInteger $1 || isFloat $1; }

Solution 21 - Linux

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_03.html

You can also use bash's character classes.

if [[ $VAR = *[[:digit:]]* ]]; then
 echo "$VAR is numeric"
else
 echo "$VAR is not numeric"
fi

Numerics will include space, the decimal point, and "e" or "E" for floating point.

But, if you specify a C-style hex number, i.e. "0xffff" or "0XFFFF", [[:digit:]] returns true. A bit of a trap here, bash allows you do to something like "0xAZ00" and still count it as a digit (isn't this from some weird quirk of GCC compilers that let you use 0x notation for bases other than 16???)

You might want to test for "0x" or "0X" before testing if it's a numeric if your input is completely untrusted, unless you want to accept hex numbers. That would be accomplished by:

if [[ ${VARIABLE:1:2} = "0x" ]] || [[ ${VARIABLE:1:2} = "0X" ]]; then echo "$VAR is not numeric"; fi

Solution 22 - Linux

I use printf as other answers mentioned, if you supply the format string "%f" or "%i" printf will do the checking for you. Easier than reinventing the checks, the syntax is simple and short and printf is ubiquitous. So its a decent choice in my opinion - you can also use the following idea to check for a range of things, its not only useful for checking numbers.

declare  -r CHECK_FLOAT="%f"  
declare  -r CHECK_INTEGER="%i"  

 ## <arg 1> Number - Number to check  
 ## <arg 2> String - Number type to check  
 ## <arg 3> String - Error message  
function check_number() { 
  local NUMBER="${1}" 
  local NUMBER_TYPE="${2}" 
  local ERROR_MESG="${3}"
  local -i PASS=1 
  local -i FAIL=0   
  case "${NUMBER_TYPE}" in 
    "${CHECK_FLOAT}") 
        if ((! $(printf "${CHECK_FLOAT}" "${NUMBER}" &>/dev/random;echo $?))); then 
           echo "${PASS}"
        else 
           echo "${ERROR_MESG}" 1>&2
           echo "${FAIL}"
        fi 
        ;;                 
    "${CHECK_INTEGER}") 
        if ((! $(printf "${CHECK_INTEGER}" "${NUMBER}" &>/dev/random;echo $?))); then 
           echo "${PASS}"
        else 
           echo "${ERROR_MESG}" 1>&2
           echo "${FAIL}"
        fi 
        ;;                 
                     *) 
        echo "Invalid number type format: ${NUMBER_TYPE} to check_number()." 1>&2
        echo "${FAIL}"
        ;;                 
   esac
} 

>$ var=45

>$ (($(check_number $var "${CHECK_INTEGER}" "Error: Found $var - An integer is required."))) && { echo "$var+5" | bc; }

Solution 23 - Linux

I like Alberto Zaccagni's answer.

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

Important prerequisites:

  • no subshells spawned
  • no RE parsers invoked
  • most shell applications don't use real numbers

But if $var is complex (e.g. an associative array access), and if the number will be a non-negative integer (most use-cases), then this is perhaps more efficient?

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

Solution 24 - Linux

You could use "let" too like this :

[ ~]$ var=1
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=01
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=toto
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s not a number
[ ~]$ 

But I prefer use the "=~" Bash 3+ operator like some answers in this thread.

Solution 25 - Linux

Almost as you want in syntax. Just need a function isnumber:

#!/usr/bin/bash

isnumber(){
  num=$1
  if [ -z "${num##*[!0-9]*}" ]; 
    then return 1
  else
    return 0
  fi
}

$(isnumber $1) && VAR=$1 || echo "need a number";
echo "VAR is $VAR"

test:

$ ./isnumtest 10
VAR is 10
$ ./isnumtest abc10
need a number
VAR is 

Solution 26 - Linux

To catch negative numbers:

if [[ $1 == ?(-)+([0-9.]) ]]
	then
	echo number
else
	echo not a number
fi

Solution 27 - Linux

printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."

Remove the -\? in grep matching pattern if you don't accept negative integer.

Solution 28 - Linux

Did the same thing here with a regular expression that test the entire part and decimals part, separated with a dot.

re="^[0-9]*[.]{0,1}[0-9]*$"

if [[ $1 =~ $re ]] 
then
   echo "is numeric"
else
  echo "Naahh, not numeric"
fi

Solution 29 - Linux

I use the following (for integers):

## ##### constants
##
## __TRUE - true (0)
## __FALSE - false (1)
##
typeset -r __TRUE=0
typeset -r __FALSE=1

## --------------------------------------
## isNumber
## check if a value is an integer 
## usage: isNumber testValue 
## returns: ${__TRUE} - testValue is a number else not
##
function isNumber {
  typeset TESTVAR="$(echo "$1" | sed 's/[0-9]*//g' )"
  [ "${TESTVAR}"x = ""x ] && return ${__TRUE} || return ${__FALSE}
}

isNumber $1 
if [ $? -eq ${__TRUE} ] ; then
  print "is a number"
fi

Solution 30 - Linux

I tried ultrasawblade's recipe as it seemed the most practical to me, and couldn't make it work. In the end i devised another way though, based as others in parameter substitution, this time with regex replacement:

[[ "${var//*([[:digit:]])}" ]]; && echo "$var is not numeric" || echo "$var is numeric"

It removes every :digit: class character in $var and checks if we are left with an empty string, meaning that the original was only numbers.

What i like about this one is its small footprint and flexibility. In this form it only works for non-delimited, base 10 integers, though surely you can use pattern matching to suit it to other needs.

Solution 31 - Linux

Quick & Dirty: I know it's not the most elegant way, but I usually just added a zero to it and test the result. like so:

function isInteger {
  [ $(($1+0)) != 0 ] && echo "$1 is a number" || echo "$1 is not a number"
 }

x=1;      isInteger $x
x="1";    isInteger $x
x="joe";  isInteger $x
x=0x16 ;  isInteger $x
x=-32674; isInteger $x   

$(($1+0)) will return 0 or bomb if $1 is NOT an integer. for Example:

function zipIt  { # quick zip - unless the 1st parameter is a number
  ERROR="not a valid number. " 
  if [ $(($1+0)) != 0 ] ; then  # isInteger($1) 
      echo " backing up files changed in the last $1 days."
      OUT="zipIt-$1-day.tgz" 
      find . -mtime -$1 -type f -print0 | xargs -0 tar cvzf $OUT 
      return 1
  fi
    showError $ERROR
}

NOTE: I guess I never thought to check for floats or mixed types that will make the entire script bomb... in my case, I didn't want it go any further. I'm gonna play around with mrucci's solution and Duffy's regex - they seem the most robust within the bash framework...

Solution 32 - Linux

I found quite a short version:

function isnum()
{
	return `echo "$1" | awk -F"\n" '{print ($0 != $0+0)}'`
}

Solution 33 - Linux

  • variable to check

    number=12345 or number=-23234 or number=23.167 or number=-345.234

  • check numeric or non-numeric

    echo $number | grep -E '^-?[0-9]*\.?[0-9]*$' > /dev/null

  • decide on further actions based on the exit status of the above

    if [ $? -eq 0 ]; then echo "Numeric"; else echo "Non-Numeric"; fi

Solution 34 - Linux

Following up on David W's answer from Oct '13, if using expr this might be better

test_var=`expr $am_i_numeric \* 0` >/dev/null 2>&1
if [ "$test_var" = "" ]
then
    ......

If numeric, multiplied by 1 gives you the same value, (including negative numbers). Otherwise you get null which you can test for

Solution 35 - Linux

Easy-to-understand and compatible solution, with test command :

test $myVariable -eq 0 2>/dev/null
if [ $? -le 1 ]; then echo 'ok'; else echo 'KO'; fi

If myVariable = 0, the return code is 0
If myVariable > 0, the return code is 1
If myVariable is not an integer, the return code is 2

Solution 36 - Linux

The accepted answer didn't work for me in all cases BASH 4+ so :

# -- is var an integer? --
# trim leading/trailing whitespace, then check for digits return 0 or 1
# Globals: None
# Arguments: string
# Returns: boolean
# --
is_int() {
	str="$(echo -e "${1}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
	case ${str} in ''|*[!0-9]*) return 1 ;; esac
	return 0
}

How to use it ?

Valid (will return 0 = true):

is_int "100" && echo "return 0" || echo "return 1"

Invalid (will return 1 = false) :

is_int "100abc" && echo "returned 0" || echo "returned 1"
is_int ""  && echo "returned 0" || echo "returned 1"
is_int "100 100"  && echo "returned 0" || echo "returned 1"
is_int "      "  && echo "returned 0" || echo "returned 1"
is_int $NOT_SET_VAR  && echo "returned 0" || echo "returned 1"
is_int "3.14"   && echo "returned 0" || echo "returned 1"

Output:

returned 0
returned 1
returned 1
returned 1
returned 1
returned 1
returned 1

note, in Bash, 1 = false, 0 = true. I am simply printing it out where instead something like this would be more likely :

if is_int ${total} ; then
    # perform some action 
fi

Solution 37 - Linux

The accepted answer does not work here, I am on MacOS. The following code works:

if [ $(echo "$number" | grep -c '^[0-9]\+$') = 0 ]; then 
    echo "it is a number"
else
    echo "not a number"
fi

Solution 38 - Linux

Stack popped a message asked me if I really want to answer after 30+ answers? But of course!!! Use bash new features and here it is: (after the comment I made a change)

> function isInt() { ([[ $1 -eq $(( $1 + 0 )) ]] 2>/dev/null && > [[ $1 != '' ]] && echo 1) || echo '' }

function isInt() {
   ([[ $1 =~ ^[-+0-9]+$  ]] && [[ $1 -eq $(( $1 + 0 )) ]] 2>/dev/null && [[ $1 != '' ]] && echo 1) || echo ''
}

Supports:

===============out-of-the-box==================
 1. negative integers (true & arithmetic),
 2. positive integers (true & arithmetic),
 3. with quotation (true & arithmetic),
 4. without quotation (true & arithmetic),
 5. all of the above with mixed signs(!!!) (true & arithmetic),
 6. empty string (false & arithmetic),
 7. no value (false & arithmetic),
 8. alphanumeric (false & no arithmetic),
 9. mixed only signs (false & no arithmetic),
================problematic====================
 10. positive/negative floats with 1 decimal (true & NO arithmetic),
 11. positive/negative floats with 2 or more decimals (FALSE & NO arithmetic).

True/false is what you get from the function only when used combined with process substitution like in [[ $( isInt <arg> ) ]] as there is no logical type in bash neither return value of function.

I use capital when the result of the test expression is WRONG whereas, it should be the reverse!

By 'arithmetic' I mean bash can do math like in this expression: $x=$(( $y + 34)).

I use 'arithmetic/no arithmetic' when in mathematical expressions the argument acts as it is expected and 'NO arithmetic' when it misbehaves compared with what it is expected.

As you see, only no 10 and 11 are the problematic ones!

Perfect!

PS: Note that the MOST popular answer fails in case 9!

Solution 39 - Linux

This is a little rough around the edges but a little more novice friendly.

if [ $number -ge 0 ]
then
echo "Continue with code block"
else
echo "We matched 0 or $number is not a number"
fi

This will cause an error and print "Illegal number:" if $number is not a number but it will not break out of the script. Oddly there is not a test option I could find to just test for an integer. The logic here will match any number that is greater than or equal to 0.

Solution 40 - Linux

Below is a Script written by me and used for a script integration with Nagios and it is working properly till now

#!/bin/bash
# Script to test variable is numeric or not
# Shirish Shukla
# Pass arg1 as number
a1=$1
a=$(echo $a1|awk '{if($1 > 0) print $1; else print $1"*-1"}')
b=$(echo "scale=2;$a/$a + 1" | bc -l 2>/dev/null)
if [[ $b > 1 ]]
then
    echo "$1 is Numeric"
else
    echo "$1 is Non Numeric"
fi

EG:

# sh isnumsks.sh   "-22.22"
-22.22 is Numeric

# sh isnumsks.sh   "22.22"
22.22 is Numeric

# sh isnumsks.sh   "shirish22.22"
shirish22.22 is Non  Numeric

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFl&#225;vio AmieiroView Question on Stackoverflow
Solution 1 - LinuxCharles DuffyView Answer on Stackoverflow
Solution 2 - LinuxjillesView Answer on Stackoverflow
Solution 3 - LinuxAlberto ZaccagniView Answer on Stackoverflow
Solution 4 - Linuxglenn jackmanView Answer on Stackoverflow
Solution 5 - LinuxmrucciView Answer on Stackoverflow
Solution 6 - LinuxF. HauriView Answer on Stackoverflow
Solution 7 - LinuxpixelbeatView Answer on Stackoverflow
Solution 8 - LinuxSergio AbreuView Answer on Stackoverflow
Solution 9 - Linuxtriple_rView Answer on Stackoverflow
Solution 10 - Linuxuser2683246View Answer on Stackoverflow
Solution 11 - LinuxJoseph ShihView Answer on Stackoverflow
Solution 12 - LinuxHachiView Answer on Stackoverflow
Solution 13 - LinuxSammitchView Answer on Stackoverflow
Solution 14 - LinuxD_IView Answer on Stackoverflow
Solution 15 - LinuxoverflowedView Answer on Stackoverflow
Solution 16 - LinuxAuloView Answer on Stackoverflow
Solution 17 - LinuxkarttuView Answer on Stackoverflow
Solution 18 - LinuxDavid W.View Answer on Stackoverflow
Solution 19 - LinuxAndrewView Answer on Stackoverflow
Solution 20 - Linux3roncoView Answer on Stackoverflow
Solution 21 - LinuxultrasawbladeView Answer on Stackoverflow
Solution 22 - Linuxuser4401178View Answer on Stackoverflow
Solution 23 - Linuxuser3895088View Answer on Stackoverflow
Solution 24 - LinuxIdriss NeumannView Answer on Stackoverflow
Solution 25 - LinuxDaniil LobanView Answer on Stackoverflow
Solution 26 - Linuxuser28490View Answer on Stackoverflow
Solution 27 - LinuxAne DijitakView Answer on Stackoverflow
Solution 28 - LinuxJeromeView Answer on Stackoverflow
Solution 29 - LinuxMarnixView Answer on Stackoverflow
Solution 30 - LinuxataView Answer on Stackoverflow
Solution 31 - LinuxWWWIZARDSView Answer on Stackoverflow
Solution 32 - LinuxmaryView Answer on Stackoverflow
Solution 33 - LinuxAtanuView Answer on Stackoverflow
Solution 34 - LinuxJon TView Answer on Stackoverflow
Solution 35 - LinuxfragadassView Answer on Stackoverflow
Solution 36 - LinuxMike QView Answer on Stackoverflow
Solution 37 - LinuxneevekView Answer on Stackoverflow
Solution 38 - LinuxcenturianView Answer on Stackoverflow
Solution 39 - LinuxIan HView Answer on Stackoverflow
Solution 40 - LinuxShirish ShuklaView Answer on Stackoverflow