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?
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?
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]+)?$
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 (?*-*
/ *.*.*
)
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):
[[ ]]
instead of [ ]
will always evaluate to true
true
bash: [[: 1 a: syntax error in expression (error token is \"a\")
bash: [[: i: expression recursion level exceeded (error token is \"i\")
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\";
Nobody suggested bash\'s extended pattern matching:
[[ $1 == ?(-)+([0-9]) ]] && echo \"$1 is an integer\"
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.
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]+$`
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
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
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.
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.
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.
[[ $1 =~ ^-?[0-9]+$ ]] && echo \"number\"
Don\'t forget -
to include negative numbers!
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
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
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.
The simplest 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
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; }
This can be achieved by using grep
to see if the variable in question matches an extended regular expression.
1120
:yournumber=1120
if [ $(echo \"$yournumber\" | grep -qE \'^[0-9]+$\'; echo $?) -ne \"0\" ]; then
echo \"Error: not a number.\"
else
echo \"Valid number.\"
fi
Output: Valid number.
1120a
:yournumber=1120a
if [ $(echo \"$yournumber\" | grep -qE \'^[0-9]+$\'; echo $?) -ne \"0\" ]; then
echo \"Error: not a number.\"
else
echo \"Valid number.\"
fi
Output: Error: not a number.
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.grep
, the -q
quiet switch turns off any output whether or not it finds anything.$?
is exit status of the previous executed command. 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;$()
is a subshell which allows us to execute another command and then use the output.So putting it all together, in a $()
subshell, 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. We then echo
the $?
exit status, which would be 0
if grep
successfully found a match and 1
if it didn\'t.
Now, outside of the $()
subshell and back in the if
conditional, we take the output, either 0
or 1
from the $()
subshell and check if it is -ne
not equal to \"0\"
. If it failed to match, the exit status will be 1
which does not match \"0\"
. Then, we will echo \"Error: not a number.\"
. If succeeded to match, the exit status output would be 0
which is equal to \"0\"
and in that else case we echo \"Valid number.\"
.
We can just change the regular expression from \'^[0-9]+$\'
to \'^[0-9]*+\\.?[0-8]+$\'
for floats or doubles.
1120.01
:yournumber=1120.01
if [ $(echo \"$yournumber\" | grep -qE \'^[0-9]*+\\.?[0-8]+$\'; echo $?) -ne \"0\" ]; then
echo \"Error: not a number.\"
else
echo \"Valid number.\"
fi
Output: Valid number.
11.20.01
:yournumber=11.20.01
if [ $(echo \"$yournumber\" | grep -qE \'^[0-9]*+\\.?[0-8]+$\'; echo $?) -ne \"0\" ]; then
echo \"Error: not a number.\"
else
echo \"Valid number.\"
fi
Output: Error: not a number.
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-8]+$\'
to \'^\\-?[0-9]*+\\.?[0-8]+$\'
.
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
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.
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...
I found quite a short version:
function isnum()
{
return `echo \"$1\" | awk -F\"\\n\" \'{print ($0 != $0+0)}\'`
}
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
To catch negative numbers:
if [[ $1 == ?(-)+([0-9.]) ]]
then
echo number
else
echo not a number
fi
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.
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; }
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 ..
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.
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