Require integer input from user - shell

I am adding two numbers using the below script, but I don't want non-numeric character entry from user so I need to test and throw an error. How do I test the user entry?
echo add 2 numbers
read sum1
read sum2
let sum3=$sum1+$sum2
echo ans is $sum3

Use printf for testing if variable is valid integer:
isInt() {
[[ -n "$1" ]] && printf '%f' "$1" >/dev/null 2>&1 &&
echo "valid integer" || echo "invalid integer";
}

Related

Bash script decrease/increase numbers [duplicate]

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):
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")
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"
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.
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 bash integer capabilities
isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
or better:
isuint_Bash() { set -- ${1//[+-]/.};(( 10#$1 >= 0 )) 2>/dev/null ;}
Using case
isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
Using 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 bash integer capabilities
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 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 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=(
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\n' Value\\Func \
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+=' ##'
else
outstr+=' --'
fi
done
printf '%-11s %s\n' "$var" "$outstr"
done
Should output:
Value\Func UPrm UGrp UBsh UCse URgx IPrm 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 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
isuint_Case 6499µs 6566µs 13065µs 100.00%
isuint_Parm 26687µs 31600µs 58287µs 446.13%
isuint_Regx 36511µs 40181µs 76692µs 587.00%
isuint_Bash 43819µs 40311µs 84130µs 643.93%
isuint_Grep 1298265µs 1224112µs 2522377µs 19306.37%
Function Number NaN Total
isint_Case 22687µs 21914µs 44601µs 100.00%
isint_Parm 35765µs 34428µs 70193µs 157.38%
isint_Regx 36949µs 42319µs 79268µs 177.73%
isint_Bash 55368µs 65095µs 120463µs 270.09%
Function Number NaN Total
isnum_Case 23313µs 23446µs 46759µs 100.00%
isnum_Parm 35677µs 42169µs 77846µs 166.48%
isnum_Regx 51864µs 69502µs 121366µs 259.56%
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 bash based solution like regex and 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).
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
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
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")
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]+$'.
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!
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.
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
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
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; }
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 ..
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.
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
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.
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
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
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

Check if getops argument is of a certain type [duplicate]

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):
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")
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"
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.
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 bash integer capabilities
isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
or better:
isuint_Bash() { set -- ${1//[+-]/.};(( 10#$1 >= 0 )) 2>/dev/null ;}
Using case
isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
Using 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 bash integer capabilities
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 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 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=(
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\n' Value\\Func \
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+=' ##'
else
outstr+=' --'
fi
done
printf '%-11s %s\n' "$var" "$outstr"
done
Should output:
Value\Func UPrm UGrp UBsh UCse URgx IPrm 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 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
isuint_Case 6499µs 6566µs 13065µs 100.00%
isuint_Parm 26687µs 31600µs 58287µs 446.13%
isuint_Regx 36511µs 40181µs 76692µs 587.00%
isuint_Bash 43819µs 40311µs 84130µs 643.93%
isuint_Grep 1298265µs 1224112µs 2522377µs 19306.37%
Function Number NaN Total
isint_Case 22687µs 21914µs 44601µs 100.00%
isint_Parm 35765µs 34428µs 70193µs 157.38%
isint_Regx 36949µs 42319µs 79268µs 177.73%
isint_Bash 55368µs 65095µs 120463µs 270.09%
Function Number NaN Total
isnum_Case 23313µs 23446µs 46759µs 100.00%
isnum_Parm 35677µs 42169µs 77846µs 166.48%
isnum_Regx 51864µs 69502µs 121366µs 259.56%
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 bash based solution like regex and 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).
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
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
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")
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]+$'.
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!
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.
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
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
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; }
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 ..
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.
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
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.
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
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
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

Bash script to check if given number is float or not using regular expressions [duplicate]

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):
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")
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"
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.
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 bash integer capabilities
isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
or better:
isuint_Bash() { set -- ${1//[+-]/.};(( 10#$1 >= 0 )) 2>/dev/null ;}
Using case
isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
Using 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 bash integer capabilities
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 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 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=(
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\n' Value\\Func \
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+=' ##'
else
outstr+=' --'
fi
done
printf '%-11s %s\n' "$var" "$outstr"
done
Should output:
Value\Func UPrm UGrp UBsh UCse URgx IPrm 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 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
isuint_Case 6499µs 6566µs 13065µs 100.00%
isuint_Parm 26687µs 31600µs 58287µs 446.13%
isuint_Regx 36511µs 40181µs 76692µs 587.00%
isuint_Bash 43819µs 40311µs 84130µs 643.93%
isuint_Grep 1298265µs 1224112µs 2522377µs 19306.37%
Function Number NaN Total
isint_Case 22687µs 21914µs 44601µs 100.00%
isint_Parm 35765µs 34428µs 70193µs 157.38%
isint_Regx 36949µs 42319µs 79268µs 177.73%
isint_Bash 55368µs 65095µs 120463µs 270.09%
Function Number NaN Total
isnum_Case 23313µs 23446µs 46759µs 100.00%
isnum_Parm 35677µs 42169µs 77846µs 166.48%
isnum_Regx 51864µs 69502µs 121366µs 259.56%
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 bash based solution like regex and 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).
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
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
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")
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]+$'.
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!
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.
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
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
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; }
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 ..
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.
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
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.
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
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
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

sign automatic number to my file in bash

I am trying to assign a unique number to my strings that are redirected and stored in a file.
You have to fill in a form, and I want to send a unique number with it.
example:
echo fill in the form
echo place
read place
date
"place: $place, time $(date) >> List
It has to look something like this.
outcome in List
number 1, place, time
number 2, place, time
number 3, place, time
I used a loop but I got the following outcome.
number 0, place, time
number 0, place, time.
I think I need a function that checks the last number given in the file and add 1 to it, but I wonder if there is an easier way.
Perhaps this one:
#!/bin/bash
# Optionally truncate file
# : > List
I=0
while
read -p "Place: " PLACE
read -p "Time: " TIME
echo "number $((++I)), $PLACE, $TIME" >> List
read -n 1 -p "Continue? " && [[ $REPLY == [yY] ]]
do
continue
done
Update:
#!/bin/bash
# Optionally truncate file
# : > List
shopt -s extglob
for (( I = 1;; ++I )); do
for (( ;; )); do
read -p "Place: " PLACE
read -p "Time: " TIME
until
read -p "Save data? "
[[ $REPLY == [nN]?([oO]) ]]
do
[[ $REPLY == [yY]?([eE][sS]) ]] && break 2
echo "Please answer Y[es] or N[o]."
done
done
echo "Saving \"number $I, $PLACE, $TIME\"."
echo "number $I, $PLACE, $TIME" >> List
until
read -p "Continue? "
[[ $REPLY == [yY]?([eE][sS]) ]]
do
[[ $REPLY == [nN]?([oO]) ]] && break 2
echo "Please answer Y[es] or N[o]."
done
echo
done
To generate a 16-char random hex string, you can use r=$(openssl rand -hex 8)
To find the last number used and increment it, you can do
prev=$(awk -F, 'END {print $1}' List)
printf "%d, place:%s, time:%s\n" $((prev+1)) "$place" "$(date)" >> List
This is subject to a race condition if the script can be executed simultaneously
To start at 1:
if [[ ! -f List ]]; then
prev=1
else
prev=$(awk -F, 'END {print $1}' List)
fi
printf "%d, place:%s, time:%s\n" $((prev+1)) "$place" "$(date)" >> List

How do I validate decimal numbers?

Hi I'm working on an assignment and got stuck on this part, how do I validate decimal numbers/numbers in shell?
It can accept numbers but not decimal numbers. I want it to be able to accept both.
This is what I have so far
if echo $value | egrep '^[0-9]+$' >/dev/null 2>&1 ; then
echo "OK"
else
echo "There Is An Error"
echo "Please Try Again"
fi
Instead of using grep, you can use the bash to check expression:
#!/bin/bash
value=98.23
if [[ "$value" =~ ^[0-9]+(\.[0-9]+)?$ ]]
then
echo good
else
echo bad
fi
use this regex instead ^[0-9]*(\.[0-9]+)?$
Using bash's pattern matching:
shopt -s extglob
while read line; do
if [[ $line == ?([-+])+([0-9])?(.*([0-9])) ]] ||
[[ $line == ?(?([-+])*([0-9])).+([0-9]) ]]
then
echo "$line is a number"
else
echo "$line NOT a number"
fi
done << END
1
-1
a
1a
1.0
1.
.0
.
-.0
+
+0
+.0
END
outputs
1 is a number
-1 is a number
a NOT a number
1a NOT a number
1.0 is a number
1. is a number
.0 is a number
. NOT a number
-.0 is a number
+ NOT a number
+0 is a number
+.0 is a number
The patterns:
optional sign, followed by one or more digits, followed optionally by a dot and zero or more digits
optional sign, followed by zero or more digits, followed by a mandatory dot, followed by one or more digits.
How about this:
if [ ! -z $(echo "$value" | grep -o "^[1-9][0-9]*\.\?[0-9]*$") ]; then echo ok; fi
-z tests for an empty string. So the negation [ ! -z "" ] will be fulfilled if the given string starts with a matching pattern.
In standard shell ([[ is non-standard) test will do the validation for you.
if test "$value" -eq 0 -o "$value" -ne 0 2> /dev/null; then
: # $value is an integer
else
: # $value is not an integer
fi
Try this: It checks negative and decimal number also.
echo $value | egrep '^-[0-9]+$|^[0-9]+$|^[0-9].[0-9]+$|^-[0-9].[0-9]+$' > /dev/null
Works in both Bash 3.0 and 4.0.
isInteger() {
[[ $1 =~ ^[0-9]+$ ]];
}
isDecimal() {
[[ $1 =~ ^[0-9]+\.[0-9]+$ ]] && ! isInteger $1;
}
computer:~ # isDecimal 123 && echo true || echo false
false
computer:~ # isDecimal 12.34 && echo true || echo false
true
computer:~ # isDecimal 12.34a && echo true || echo false
false
computer:~ # isDecimal 0.0000001 && echo true || echo false
true
To check if number, simply test against both functions.

Resources