tags:

views:

4290

answers:

8

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?

UPDATE: I managed (whit Charles' help) to do it, but I'm not yet sure it's the best way to do that (even though it worked on my tests). This is how it ended up:

[[ $1 =~ "^[0-9]+$" ]] && echo "number" && exit 0 || echo "not a number" && exit 1
+12  A: 

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

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

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

^[0-9]+([.][0-9]+)?$
Charles Duffy
+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".
SourceRebels
@DirtyAffairs - good call, updated appropriately.
Charles Duffy
I find the ''exec >
lhunath
@lhunath - true 'nuff. I tend to use it in more complex error handlers (ie. much more than just one "echo" following), but the habit leaked out here.
Charles Duffy
+1  A: 

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.

+2  A: 

I use this:

$var -eq $var

as in:

#!/bin/bash

var=a

if [ $var -eq $var 2>/dev/null ]; then
  echo number
else
  echo not a 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.

EDIT: This approach do not behave correctly with numbers with decimal point. My fault.

Alberto Zaccagni
+1  A: 

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
Marnix
A: 

This tests if a number is a non negative integer and is both 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";

BUT IS WRONG.
As jilles commented and suggested in his answer this is the correct way to do it using shell-patterns.

[ ! -z "${num##*[!0-9]*}" ] && echo "is a number" || echo "is not a number";
mrucci
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
Thank you very much! Answer updated.
mrucci
A: 

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
ultrasawblade
A: 

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.

Juaco
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.
Juaco
+3  A: 

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

jilles