891

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?

10
  • 21
    As an aside -- the test && echo "foo" && exit 0 || echo "bar" && exit 1 approach you're using may have some unintended side effects -- if the echo fails (perhaps output is to a closed FD), the exit 0 will be skipped, and the code will then try to echo "bar". If it fails at that too, the && condition will fail, and it won't even execute exit 1! Using actual if statements rather than &&/|| is less prone to unexpected side effects. Commented Aug 24, 2011 at 14:12
  • 13
    Bit late to the party, but I know about the dangers that Charles wrote about, as I had to go through them quite some time ago too. So here's a 100% fool-proof (and well-readable) line for you: [[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; } The curly brackets indicate that things should NOT be executed in a subshell (which would definitely be that way with () parentheses used instead). Caveat: Never miss the final semicolon. Otherwise you might cause bash to print out the ugliest (and most pointless) error messages... Commented Jun 9, 2015 at 17:34
  • 9
    It doesn't work in Ubuntu, unless you don't remove the quotes. So it should just be [[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
    – Treviño
    Commented Sep 10, 2015 at 10:56
  • 8
    You'll need to be more specific about what you mean by "number". An integer? A fixed-point number? Scientific ("e") notation? Is there a required range (e.g. a 64-bit unsigned value), or do you allow any number that can be written? Commented Nov 15, 2016 at 11:45
  • 2
    Bash does provide a reliable means of determining if a number is an INTEGER. { VAR="asdfas" ; (( VAR )) ; echo $?; } The equation will correctly fail if the answer is '0' because '0' is not an integer. I had the very same problem just a few minutes ago and found this thread with a quick serarch. I hope this helps others. Other people were close though. Commented Jun 26, 2021 at 21:08

42 Answers 42

1152

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]+)?$
38
  • 12
    +1 for this approach, but take care with decimals, doing this test with, by example, "1.0" or "1,0" prints "error: Not a number". Commented Apr 30, 2009 at 14:30
  • 5
    @Ben do you really want to handle more than one minus sign? I'd make it ^-? rather than ^-* unless you're actually doing the work to handle multiple inversions correctly. Commented Jun 26, 2011 at 22:57
  • 60
    I'm not sure why the regular expression has to be saved in a variable, but if it's for the sake of compatibility I don't think it's necessary. You could just apply the expression directly: [[ $yournumber =~ ^[0-9]+$ ]].
    – konsolebox
    Commented Aug 31, 2013 at 21:48
  • 10
    @konsolebox yes, compatibility. Backslash handling in literal regular expressions on the right-hand side of =~ changed between 3.1 and 3.2, whereas backslash handling in assignments is constant in all relevant releases of bash. Thus, following the practice of always assigning regular expressions to variables before matching against them using =~ avoids surprises. I do it here to teach good habits, even though this particular regex has no backslash escapes. Commented Sep 1, 2013 at 0:54
  • 6
    @FrozenFlame, putting the right-hand side of =~ in quotes makes it no longer a regular expression but a regular string (in modern versions of bash but not some ancient ones), whereas the version I gave in this answer works consistently in every release where =~ is supported at all. Commented Aug 1, 2014 at 13:50
414
+550

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 (?*-* / *.*.*)

8
  • 27
    +1 -- this is idiomatic, portable way back to the original Bourne shell, and has built-in support for glob-style wildcards. If you come from another programming language, it looks eerie, but it's much more elegant than coping with the brittleness of various quoting issues and endless backwards/sideways compatibility problems with if test ...
    – tripleee
    Commented Sep 4, 2011 at 13:21
  • 6
    You can change the first line to ${string#-} (which doesn't work in antique Bourne shells, but works in any POSIX shell) to accept negative integers. Commented Jan 3, 2012 at 17:17
  • 5
    Also, this is easy to extend to floats -- just add '.' | *.*.* to the disallowed patterns, and add dot to the allowed characters. Similarly, you can allow an optional sign before, although then I would prefer case ${string#[-+]} to simply ignore the sign.
    – tripleee
    Commented Jun 7, 2014 at 12:10
  • 3
    @Dor The quotes are not needed, since the case command does not perform word splitting and pathname generation on that word anyway. (However, expansions in case patterns may need quoting since it determines whether pattern matching characters are literal or special.)
    – jilles
    Commented Oct 7, 2016 at 15:47
  • 2
    Explanation for the pipe(vertical bar) character: unix.stackexchange.com/questions/85939/…
    – Roland
    Commented Feb 19, 2020 at 14:43
271

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")
20
  • 11
    I'd still recommend this (but with the variables quoted to allow for empty strings), since the result is guaranteed to be usable as a number in Bash, no matter what.
    – l0b0
    Commented Dec 24, 2010 at 8:43
  • 23
    Take care to use single brackets; [[ a -eq a ]] evaluates to true (both arguments get converted to zero)
    – Tgr
    Commented Aug 28, 2012 at 9:30
  • 3
    Very nice! Note this this only works for an integer, not any number. I needed to check for a single argument which must be an integer, so this worked well: if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
    – haridsv
    Commented Aug 2, 2013 at 13:07
  • 6
    I would strongly advise against this method because of the not insignificant number of shells whose [ builtin will evaluate the arguments as arithmetic. That is true in both ksh93 and mksh. Further, since both of those support arrays, there is easy opportunity for code injection. Use a pattern match instead.
    – ormaaj
    Commented Oct 8, 2014 at 5:34
  • 3
    @AlbertoZaccagni, in current releases of bash, these values are interpreted with numeric-context rules only for [[ ]] but not for [ ]. That said, this behavior is unspecified by both the POSIX standard for test and in bash's own documentation; future versions of bash could modify behavior to match ksh without breaking any documented behavioral promises, so relying on its current behavior persisting is not guaranteed to be safe. Commented Mar 26, 2016 at 18:43
84

Nobody suggested bash's extended pattern matching:

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

or using a POSIX character class:

[[ $1 == ?(-)+([[:digit:]]) ]] && echo "$1 is an integer"
20
  • 7
    Glenn, I remove shopt -s extglob from your post (that I upvoted, it's one of my favorite answers here), since in Conditional Constructs you can read: When the == and != operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled. I hope you don't mind! Commented Feb 13, 2015 at 19:49
  • 3
    @Jdamian: you're right, this was added in Bash 4.1 (which was released at the end of 2009… Bash 3.2 was released in 2006… it's now an antique software, sorry for those who are stuck in the past). Also, you could argue that extglobs where introduced in version 2.02 (released in 1998), and don't work in <2.02 versions… Now your comment here will serve as a caveat regarding older versions. Commented Sep 8, 2016 at 9:22
  • 1
    Variables within [[...]] are not subject to word splitting or glob expansion. Commented Jan 14, 2020 at 15:25
  • 1
    @ThiagoConrado, look up [[...]] in the manual (or help [[ at a bash prompt): only the right-hand side of == is a pattern. Commented Dec 8, 2020 at 0:03
  • 1
    Use [[:digit:]] instead of [:digit:] for POSIX.
    – bugi
    Commented Feb 14, 2022 at 21:02
82
+500

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. Mostly:

Unsigned Integer is_uint()

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

Signed integer is_int()

These functions 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 integer capabilities

    Something like Alberto Zaccagni's answer based on integer:

    isint_Shell() {  [ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null;}
    

    Or using bashisms:

    isint_Bash() {  set -- "${1//[!+-]}" ${1#${1//[!+-]}};
                    (( ( 0 ${1:-+} 10#$2 ) ? 1:1 )) 2>/dev/null ;}
    
  • Using case

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

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

Number (unsigned float) is_num()

These functions 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 '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 the previously declared functions.)

testcases=(
    0  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 %4s\n' \
    Value\\Func U{Prm,Grp,Bsh,Cse,Rgx} I{Prm,Shl,Bsh,Cse,Rgx} N{Prm,Cse,Rgx};\
for var in "${testcases[@]}";do
    outstr='';
    for func in isuint_{Parm,Grep,Bash,Case,Regx} \
          isint_{Parm,Shell,Bash,Case,Regx} isnum_{Parm,Case,Regx};do
        if $func "$var"; then
            outstr+='   ##'
        else
            outstr+='   --'
        fi
    done
    printf '%-11s %s\n' "$var" "$outstr"
done

Should output:

Value\Func   UPrm UGrp UBsh UCse URgx IPrm IShl IBsh ICse IRgx NPrm NCse NRgx
0              ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##
1              ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##
42             ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##
-3             --   --   --   --   --   ##   ##   ##   ##   ##   ##   ##   ##
+42            --   --   --   --   --   ##   ##   ##   ##   ##   ##   ##   ##
+3.            --   --   --   --   --   --   --   --   --   --   ##   ##   ##
.9             --   --   --   --   --   --   --   --   --   --   ##   ##   ##
3.14           --   --   --   --   --   --   --   --   --   --   ##   ##   ##
+3.141         --   --   --   --   --   --   --   --   --   --   ##   ##   ##
-31.4          --   --   --   --   --   --   --   --   --   --   ##   ##   ##
               --   --   --   --   --   --   --   --   --   --   --   --   --
.              --   --   --   --   --   --   --   --   --   --   --   --   --
3-3            --   --   --   --   --   --   --   ##   --   --   --   --   --
3.1.4          --   --   --   --   --   --   --   --   --   --   --   --   --
3a             --   --   --   --   --   --   --   --   --   --   --   --   --
a3             --   --   --   --   --   --   --   --   --   --   --   --   --
blah           --   --   --   --   --   --   --   --   --   --   --   --   --
Good day!      --   --   --   --   --   --   --   --   --   --   --   --   --

I hope!

Note: uint_bash seems not perfect! In detail: gniourf_gniourf's second comment rightly said:

All methods using arithmetic context are subject to arbitrary code injection and are very dangerous! All the *_Bash functions here are dangerous and should be marked as ANTI=PATTERNS and "DANGEROUS: SUBJECT TO ARBITRARY CODE INJECTION. SHOULD NOT BE USED UNDER ANY CIRCUMSTANCES".

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 Shell 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
isuint_Case       1763µs      1535µs          3298µs    100.00%
isuint_Parm       2571µs      3319µs          5890µs    178.59%
isuint_Regx       5014µs      5952µs         10966µs    332.50%
isuint_Bash       6379µs      6106µs         12485µs    378.56%
isuint_Grep     293347µs    287733µs        581080µs  17619.16%

Function          Number         NaN          Total
isint_Case        2020µs      1938µs          3958µs    100.00%
isint_Parm        3291µs      4707µs          7998µs    202.07%
isint_Shell       5775µs      5183µs         10958µs    276.86%
isint_Regx        5371µs      5836µs         11207µs    283.15%
isint_Bash        8946µs      7718µs         16664µs    421.02%

Function          Number         NaN          Total
isnum_Case        2181µs      2232µs          4413µs    100.00%
isnum_Parm        4502µs      6017µs         10519µs    238.36%
isnum_Regx        8247µs     13019µs         21266µs    481.89%

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

Conclusion

  • The case way is clearly the quickest! About 3x quicker than regex and 2x quicker than using parameter expansion.
  • forks (to grep or any binaries) should be avoided when not needed, mostly for small strings, a single line, or other one shot operations.

The 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 based solution like regex and '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).

17
  • 1
    @tripleee redo the test like published there, many time when my desk was relatively quiet, then chosed more relevant output to publish there. (Did you try my script? I've added a +Numberr column, I won't try to explain this there;) Commented Jun 28, 2021 at 13:10
  • 1
    Based on these solutions, you could expand to full float comparisions which would include scientific notations: is_float() { is_num "${1/[eE][-+]/}"; }
    – kvantour
    Commented Jun 5, 2022 at 10:16
  • 1
    the problem comes in the arithmetic context: isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;} and then isuint_Bash '1 && 1' && echo "Is a number"' so according to this, 1 && 1 is a number, and worse: isuint_Bash '1 && a[$(touch I_JUST_TOUCHED_A_FILE_LOL)]' && echo "Is a number" will say it's a number and touch a file :( Commented Jan 27 at 8:54
  • 1
    I agree that the case method is very good! just wanted to point out the code injection issue when using the arithmetic context method. All your methods using arithmetic context are subject to arbitrary code injection and are very dangerous! all the *_Bash functions you gave are dangerous and should be marked as ANTI-PATTERNS and "DANGEROUS: SUBJECT TO ARBITRARY CODE INJECTION. SHOULD NOT BE USED UNDER ANY CIRCUMPSTANCES". Please do another edit to clearly mention this security issue for all the _Bash functions! Commented Jan 27 at 9:22
  • 1
    @gniourf_gniourf Answer edited again! (Just copy the more relevant of your comment) Thanks for driving me to this! (Not well tested as they don't show interesting perfs, but you do! Thanks again.) Commented Jan 27 at 9:38
57

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.

6
  • 6
    This does not work properly, it accepts any string starting with a digit. Note that WORD in ${VAR##WORD} and similar is a shell pattern, not a regular expression.
    – jilles
    Commented Oct 16, 2010 at 22:46
  • 3
    Can you translate that expression into English, please? I really want to use it, but I don't understand it enough to trust it, even after perusing the bash man page.
    – CivFan
    Commented May 25, 2016 at 21:56
  • 4
    *[!0-9]* is a pattern that matches all strings with at least 1 non-digit character. ${num##*[!0-9]*} is a "parameter expansion" where we take the content of the num variable and remove the longest string that matches the pattern. If the result of the parameter expansion is not empty (! [ -z ${...} ]) then it's a number since it does not contain any non-digit character.
    – mrucci
    Commented May 26, 2016 at 19:00
  • Unfortunately this fails if there any digits in the argument, even if it is not valid number. For example "exam1ple" or "a2b".
    – studgeek
    Commented Jan 6, 2017 at 0:59
  • 2
    But that's good, because "exam1ple", "a2b" and "122s" are all not numbers.
    – algalg
    Commented Aug 15, 2021 at 14:19
27

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.

17
  • 4
    isnumber(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; } Commented Sep 28, 2012 at 18:33
  • 4
    @sputnick your version breaks the inherent (and useful) return value semantics of the original function. So, instead, simply leave the function as-is, and use it: isnumber 23 && echo "this is a number" || echo "not a number"
    – michael
    Commented Jul 18, 2013 at 23:54
  • 4
    Shouldn't this have also 2>/dev/null, so that isnumber "foo" does not pollute stderr?
    – gioele
    Commented Jun 6, 2014 at 6:10
  • 4
    To call modern shells like bash "a DSL for controlling files and processes" is ignoring that they're used for much more than that - some distros have built entire package managers and web interfaces on it (as ugly as that might be). Batch files fit your description though, as even setting a variable there is difficult. Commented Jun 26, 2014 at 10:57
  • 7
    It's funny that you're trying to be smart by copying some idioms from other languages. Unfortunately this doesn't work in shells. Shells are very special, and without solid knowledge about them, you're likely to write broken code. Your code is broken: isnumber "'a" will return true. This is documented in the POSIX spec where you'll read: If the leading character is a single-quote or double-quote, the value shall be the numeric value in the underlying codeset of the character following the single-quote or double-quote. Commented Feb 13, 2015 at 19:37
22

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]+$`
2
  • The solutions using awk by triple_r and tripleee work with floats. Commented Aug 27, 2017 at 18:24
  • Thanks for this and very good point! Cause the question is actually how to check if it is a number and not just an integer.
    – Tanasis
    Commented Apr 1, 2020 at 20:01
14

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
1
  • 5
    Printing a number is less useful than setting an exit code. 'BEGIN { exit(1-(a==a+0)) }' is slightly hard to grok but can be used in a function which returns true or false just like [, grep -q, etc.
    – tripleee
    Commented Aug 2, 2017 at 7:01
11
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
4
  • Thumbs up especially for the man -P 'less +/parameter\/' bash part. Learning something new every day. :)
    – David
    Commented Apr 11, 2019 at 10:09
  • 1
    @sjas You could easily add \- in regular expression to address the issue. Use [0-9\-\.\+] to account for floats and signed numbers. Commented May 9, 2019 at 14:42
  • @sjas ok, my fault Commented May 9, 2019 at 14:52
  • @sjas echo $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' && echo yes || echo no Commented May 9, 2019 at 15:15
11

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")
1
  • 2
    This is wrong (buggy)! Try this: foo=1;set -- foo;(( $1 )) 2>/dev/null && echo "'$1' is a number" Commented Jun 25, 2021 at 14:55
10

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.

3
  • This fails for an empty var. Commented Feb 13, 2015 at 21:27
  • Or for variables with trailing newlines or something like $'0\n\n\n1\n\n\n2\n\n\n3\n'. Commented Feb 13, 2015 at 21:44
  • Requiring multiple external processes for something the shell is perfectly capable of processing using pure builtins is just bad practice.
    – tripleee
    Commented Jun 28, 2021 at 5:16
10

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]+$'.

4
  • 1
    LGTM; answer as-edited has my +1. The only things I'd do differently at this point are just matters of opinion rather than correctness (f/e, using [-] instead of \- and [.] instead of \. is a little more verbose, but it means your strings don't have to change if they're used in a context where backslashes get consumed). Commented Mar 16, 2020 at 15:07
  • I was using a different approach with if [[ $yournumber =~ ^[0-9]+([.][0-9]+)?$ ]] ; then in an old Ubuntu 14.04 based system but, somehow, it stopped working after upgrading to Ubuntu 20.04, your first solution for "Test Integer" does the same in 20.04. I can't say if it is related to the upgrade or maybe my script was wrong in first instance and -somehow- yet working in the old system. Thank you very much. Commented Jul 7, 2020 at 4:02
  • @GeppettvsD'Constanzo, perhaps might the script have been using #!/bin/sh? If so, it should still work in modern Ubuntu as long as you use a #!/bin/bash shebang, and avoid starting scripts with sh scriptname (which ignores the shebang and forces use of sh instead of bash). Commented Aug 11, 2020 at 16:00
  • Using an external process for something Bash has built in is always dubious.
    – tripleee
    Commented Jun 24, 2021 at 3:56
7

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.

6
  • 2
    either duplicate of, or perhaps better suited as a comment to, pixelbeat's answer (using %f is probably better anyway)
    – michael
    Commented Jul 19, 2013 at 0:01
  • 4
    Instead of checking the previous status code, why not just put it in the if itself? That's what if does... if printf "%g" "$var" &> /dev/null; then ... Commented Jun 26, 2014 at 11:00
  • 3
    This has other caveats. It will validate the empty string, and strings like 'a. Commented Feb 13, 2015 at 21:30
  • Best solution, in my book. I tried bc before realising bc doesn't do floats. The interpretation of the empty string as a number is a minor caveat (and "a" is not interpreted as a number). Commented Jul 17, 2020 at 7:52
  • @JPGConnly, what do you mean "bc doesn't do floats"?
    – Jdamian
    Commented Sep 11, 2020 at 9:51
7

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.

0
7

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
0
7
[[ $1 =~ ^-?[0-9]+$ ]] && echo "number"

Don't forget - to include negative numbers!

4
  • What is the minimum version of bash? I just get bash: conditional binary operator expected bash: syntax error near unexpected token `=~' Commented Nov 28, 2011 at 20:11
  • 1
    @PaulHargreaves =~ existed at least as far back as bash 3.0. Commented Aug 22, 2014 at 22:45
  • @PaulHargreaves you probably had a problem with your first operand, e.g. too many quotation marks or similar Commented Feb 4, 2015 at 22:34
  • @JoshuaClayton I asked about the version because it's very very old bash on a Solaris 7 box, which we still have and it doesn't support =~ Commented Feb 6, 2015 at 8:10
6

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.

5
  • 2
    This is rather bad. To make it slightly better you should use expr -- "$number" + 0; yet this will still pretend that 0 isn't a number. From man expr: Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0, Commented Feb 13, 2015 at 21:23
  • With Bash, you really should never need expr. If you are confined to a lesser Bourne shell like POSIX sh, then maybe.
    – tripleee
    Commented Jun 24, 2021 at 3:58
  • POSIX sh is guaranteed to have $(( )). You're talking 1970s Bourne to need expr. Commented Jun 24, 2022 at 19:20
  • @tripleee - May I ask why expr is not encouraged in Bash? I think this solution is POSIX compliant and the most concise. Yes it fails when number=0. It can be improved by expr 1 + "$v" \* "$v" which ensures the answer is not zero.
    – midnite
    Commented Mar 1 at 11:35
  • Because the external kitchen sink called expr made sense for th original Bourne shell because it did not have equivalent or superior features built in; but that is no longer true for modern shells. Again, if you are shooting for POSIX compliance, there may be corner cases where there are no convenient equivalents to expr, but adding two integers is not one of them.
    – tripleee
    Commented Mar 1 at 15:08
6

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
6
  • 2
    To handle negative numbers would require a more complicated approach.
    – Andrew
    Commented May 8, 2017 at 14:28
  • ... Or an optional positive sign.
    – tripleee
    Commented Aug 2, 2017 at 6:52
  • @tripleee i'd like to see your approach if you know how to do it.
    – Andrew
    Commented Aug 2, 2017 at 15:08
  • 1
    @andrew with this little change, your code work (using zsh) very fine! Even for negative or positive numbers: [[ ! -n ${1//[+\-0-9]/} ]] && echo "is a number" || echo "is not a number";. The problem now is that +-123 will pass too. Commented Jul 1, 2022 at 21:29
  • 1
    Finally, I achieved expected result using some more changes beginning from your answer. Hope to help someone more. gist.github.com/bernardolm/1c9e003a5f68e6e2534458fa758a096d Commented Jul 1, 2022 at 22:08
5

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
5
  • 20
    [[ $VAR = *[[:digit:]]* ]] will return true if the variable contains a number, not if it is an integer. Commented Oct 26, 2012 at 14:48
  • [[ "z3*&" = *[[:digit:]]* ]] && echo "numeric" prints numeric. Tested in bash version 3.2.25(1)-release.
    – Jdamian
    Commented Sep 8, 2016 at 7:13
  • 1
    @ultraswadable, your solution detects those strings containing, at least, one digit surrounded (or not) by any other characters. I downvoted.
    – Jdamian
    Commented Sep 8, 2016 at 7:28
  • The obviously correct approach is therefore to reverse this, and use [[ -n $VAR && $VAR != *[^[:digit:]]* ]]
    – eschwartz
    Commented Oct 23, 2018 at 15:16
  • 1
    @eschwartz , your solution doesn't work with negative numbers
    – Angel
    Commented Mar 21, 2019 at 21:57
5

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; }

5

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; }
6
  • Personally I would remove the function keyword as it doesn't do anything useful. Also, I'm not sure about the usefulness of the return values. Unless otherwise specified, the functions will return the exit status of the last command, so you don't need to return anything yourself.
    – Tom Fenech
    Commented Sep 9, 2016 at 16:39
  • Nice, indeed the returns are confusing and make it less readable. Using function keywords or not is more a question of personal flavor at least i removed them from the one liners to save some space. thx.
    – 3ronco
    Commented Sep 9, 2016 at 17:07
  • Don't forget that semicolons are needed after the tests for the one-line versions.
    – Tom Fenech
    Commented Sep 9, 2016 at 17:09
  • 3
    isNumber will return 'true' on any string that has a number in it. Commented Oct 19, 2016 at 7:13
  • @DrStrangepork Indeed, my false_values array is missing that case. I will have look into it. Thanks for the hint.
    – 3ronco
    Commented Oct 24, 2016 at 8:05
4

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 ..
1
  • This doesn't fail only for complex numbers (those with an imaginary component), but also for floating point numbers (those with a non-integer component). Commented May 7, 2021 at 22:55
2

To catch negative numbers:

if [[ $1 == ?(-)+([0-9.]) ]]
    then
    echo number
else
    echo not a number
fi
3
  • Also, this requires extended globbing to be enabled first. This is a Bash-only feature which is disabled by default.
    – tripleee
    Commented Aug 2, 2017 at 8:38
  • @tripleee extended globbing is activated automatically when using == or != When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled. gnu.org/software/bash/manual/bashref.html#index-_005b_005b Commented May 13, 2019 at 3:38
  • @BadrElmers Thanks for the update. This seems to be a new behavior which is not true in my Bash 3.2.57 (MacOS Mojave). I see it works as you describe in 4.4.
    – tripleee
    Commented May 13, 2019 at 5:00
2

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.

2
  • 5
    This is very dangerous. Don't evaluate unvalidated arithmetic in the shell. It must be validated some other way first.
    – ormaaj
    Commented Oct 8, 2014 at 5:27
  • @ormaaj why is it dangerous? As in malicious numbers, or overflows? Is it dangerous when the input is your own value?
    – balupton
    Commented Sep 3, 2021 at 8:03
2

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 

UPDATE 2024 (it still doesn't support floating point numbers)

#!/usr/bin/bash

isnumber(){
  if [ -z "$1" ]; then
    echo "{empty string} isn't number"
    return 1
  elif [ -z "${1##*[!0-9]*}" ]; then
    echo "$1 isn't number"
    return 1
  else
    echo "$1 is number" 
    return 0
  fi
}

isnumber "$1"
7
  • This incorrectly reports that an empy string is a number.
    – tripleee
    Commented Mar 24 at 10:00
  • @tripleee how did you run the test? if I run ./isnumtest or ./isnumtest "" I get need a number Commented Mar 24 at 12:42
  • Sorry, I misread your logic. The calling code has multiple flaws, though; see When to wrap quotes around a shell variable?. The idiomatic formulation would be isnumber "$1" && var=$1 || echo "Need a number: $1" >&2 (see also stackoverflow.com/questions/673055/…)
    – tripleee
    Commented Mar 24 at 13:09
  • In Bash, you would also declare local num to avoid clobbering any global variable with the same name.
    – tripleee
    Commented Mar 24 at 13:11
  • 1
    Thanks for updating. I fixed the remaining ones. (Funny; the ones you missed were the more crucial ones.)
    – tripleee
    Commented May 3 at 4:52
1
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.

1
  • 3
    Downvote for lack of explanation. How does this work? It looks complex and brittle, and it's not obvious what inputs exactly it will accept. (For example, is removing spaces crucially necessary? Why? It will say a number with embedded spaces is a valid number, which may not be desirable.)
    – tripleee
    Commented Aug 2, 2017 at 6:51
0

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
2
  • 1
    Almost correct (you're accepting the empty string) but gratutiously complicated to the point of obfuscation. Commented Jan 3, 2012 at 17:16
  • 3
    Incorrect: you're accepting -n, etc. (because of echo), and you're accepting variables with trailing newlines (because of $(...)). And by the way, print is not a valid shell command. Commented Feb 13, 2015 at 21:40
0

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.

2
  • Reading mrucci's solution, it looks almost the same as mine, but using regular string replacement instead of "sed style". Both use the same rules for pattern matching and are, AFAIK, interchangeable solutions.
    – ata
    Commented Oct 16, 2010 at 22:41
  • sed is POSIX, while your solution is bash. Both have their uses
    – v010dya
    Commented Apr 11, 2020 at 4:43
0

I found quite a short version:

function isnum()
{
    return `echo "$1" | awk -F"\n" '{print ($0 != $0+0)}'`
}
1
  • um.. doesn't this just return 0 if the string is not a number? Does that means it doesn't work if your string is "0"?
    – naught101
    Commented May 30, 2012 at 4:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.