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]+)?$
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 truetruebash: [[: 1 a: syntax error in expression (error token is "a")bash: [[: i: expression recursion level exceeded (error token is "i")(2nd) Full rewrite of this answer: Jun 2021 27.
There are some strongly different methods regarding different kinds of tests.
I reviewed most relevant methods and built this comparison.
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 ;}
Using case
isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
Using bash's regex
isuint_Regx() { [[ $1 =~ ^[0-9]+$ ]] ;}
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() { (( 10#$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]+$ ]] ;}
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 ;}
(You could copy/paste this test code after previous declared functions.)
testcases=(
1 42 -3 +42 +3. .9 3.14 +3.141 -31.4 '' . 3-3 3.1.4 3a a3 blah 'Good day!'
);printf '%-12s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s\n' Function \
U{Prm,Grp,Bsh,Cse,Rgx} I{Prm,Bsh,Cse,Rgx} N{Prm,Cse,Rgx}; \
for var in "${testcases[@]}";do
outstr='';
for func in isuint_{Parm,Grep,Bash,Case,Regx} isint_{Parm,Bash,Case,Regx} \
isnum_{Parm,Case,Regx};do
if $func "$var"
then outstr+=' num'
else outstr+=' str'
fi
done
printf '%-11s %s\n' "|$var|" "$outstr"
done
Should output:
Function UPrm UGrp UBsh UCse URgx IPrm IBsh ICse IRgx NPrm NCse NRgx
|1| num num num num num num num num num num num num
|42| num num num num num num num num num num num num
|-3| str str str str str num num num num num num num
|+42| str str num str str num num num num num num num
|+3.| str str str str str str str str str num num num
|.9| str str str str str str str str str num num num
|3.14| str str str str str str str str str num num num
|+3.141| str str str str str str str str str num num num
|-31.4| str str str str str str str str str num num num
|| str str num str str str str str str str str str
|.| str str str str str str str str str str str str
|3-3| str str num str str str str str str str str str
|3.1.4| str str str str str str str str str str str str
|3a| str str str str str str str str str str str str
|a3| str str str str str str str str str str str str
|blah| str str str str str str str str str str str str
|Good day!| str str str str str str str str str str str str
I hope! (Note: uint_bash seem not perfect!)
Then I've built this test function:
testFunc() {
local tests=1000 start=${EPOCHREALTIME//.}
for ((;tests--;)) ;do
"$1" "$3"
done
printf -v "$2" %u $((${EPOCHREALTIME//.}-start))
}
percent(){ local p=00$((${1}00000/$2));printf -v "$3" %.2f%% ${p::-3}.${p: -3};}
sortedTests() {
local func NaNTime NumTime ftyp="$1" nTest="$2" tTest="$3" min i pct line
local -a order=()
shift 3
for func ;do
testFunc "${ftyp}_$func" NaNTime "$tTest"
testFunc "${ftyp}_$func" NumTime "$nTest"
order[NaNTime+NumTime]=${ftyp}_$func\ $NumTime\ $NaNTime
done
printf '%-12s %11s %11s %14s\n' Function Number NaN Total
min="${!order[*]}" min=${min%% *}
for i in "${!order[@]}";do
read -ra line <<<"${order[i]}"
percent "$i" "$min" pct
printf '%-12s %9d\U00B5s %9d\U00B5s %12d\U00B5s %9s\n' \
"${line[@]}" "$i" "$pct"
done
}
I could run in this way:
sortedTests isuint "This is not a number." 31415926535897932384 \
Case Grep Parm Bash Regx ;\
sortedTests isint "This is not a number." 31415926535897932384 \
Case Parm Bash Regx ;\
sortedTests isnum "This string is clearly not a number..." \
3.141592653589793238462643383279502884 Case Parm Regx
On my host, this shows somthing like:
Function Number NaN Total Rank
isuint_Case 8,080µs 6,848µs 14,928µs 100.00%
isuint_Parm 10,571µs 13,061µs 23,632µs 158.31%
isuint_Regx 12,865µs 15,407µs 28,272µs 189.39%
isuint_Bash 19,054µs 17,182µs 36,236µs 242.74%
isuint_Grep 1,333,786µs 1,416,626µs 2,750,412µs 18424.52%
Function Number NaN Total Rank
isint_Case 8,860µs 7,813µs 16,673µs 100.00%
isint_Parm 14,141µs 16,774µs 30,915µs 185.42%
isint_Regx 14,202µs 17,375µs 31,577µs 189.39%
isint_Bash 18,988µs 16,598µs 35,586µs 213.43%
Function Number NaN Total Rank
isnum_Case 8,935µs 9,232µs 18,167µs 100.00%
isnum_Parm 18,898µs 22,577µs 41,475µs 228.30%
isnum_Regx 25,336µs 42,825µs 68,161µs 375.19%
You could download full isnum comparission script here or full isnum comparission script as text here., (with UTF8 and LATIN handling).
case way is clearly the quickest! About 3x quicker than regex and 2x quicker than using parameter expansion.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 ;}
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).
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.
1120:yournumber=1120
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
echo "Valid number."
else
echo "Error: not a number."
fi
Output: Valid number.
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.
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.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.".
We can just change the regular expression from '^[0-9]+$' to '^[0-9]*\.?[0-9]+$' for floats or doubles.
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.
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.
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]+$'.
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
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
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
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
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...
The accepted answer didn't work for me in all cases BASH 4+ so :
# -- is var an integer? --
# trim leading/trailing whitespace, then check for digits return 0 or 1
# Globals: None
# Arguments: string
# Returns: boolean
# --
is_int() {
str="$(echo -e "${1}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
case ${str} in ''|*[!0-9]*) return 1 ;; esac
return 0
}
How to use it ?
Valid (will return 0 = true):
is_int "100" && echo "return 0" || echo "return 1"
Invalid (will return 1 = false) :
is_int "100abc" && echo "returned 0" || echo "returned 1"
is_int "" && echo "returned 0" || echo "returned 1"
is_int "100 100" && echo "returned 0" || echo "returned 1"
is_int " " && echo "returned 0" || echo "returned 1"
is_int $NOT_SET_VAR && echo "returned 0" || echo "returned 1"
is_int "3.14" && echo "returned 0" || echo "returned 1"
Output:
returned 0
returned 1
returned 1
returned 1
returned 1
returned 1
returned 1
note, in Bash, 1 = false, 0 = true. I am simply printing it out where instead something like this would be more likely :
if is_int ${total} ; then
# perform some action
fi
Stack popped a message asked me if I really want to answer after 30+ answers? But of course!!! Use bash new features and here it is: (after the comment I made a change)
function isInt() { ([[ $1 -eq $(( $1 + 0 )) ]] 2>/dev/null && [[ $1 != '' ]] && echo 1) || echo '' }
function isInt() {
([[ $1 =~ ^[-+0-9]+$ ]] && [[ $1 -eq $(( $1 + 0 )) ]] 2>/dev/null && [[ $1 != '' ]] && echo 1) || echo ''
}
Supports:
===============out-of-the-box==================
1. negative integers (true & arithmetic),
2. positive integers (true & arithmetic),
3. with quotation (true & arithmetic),
4. without quotation (true & arithmetic),
5. all of the above with mixed signs(!!!) (true & arithmetic),
6. empty string (false & arithmetic),
7. no value (false & arithmetic),
8. alphanumeric (false & no arithmetic),
9. mixed only signs (false & no arithmetic),
================problematic====================
10. positive/negative floats with 1 decimal (true & NO arithmetic),
11. positive/negative floats with 2 or more decimals (FALSE & NO arithmetic).
True/false is what you get from the function only when used combined with process substitution like in [[ $( isInt <arg> ) ]] as there is no logical type in bash neither return value of function.
I use capital when the result of the test expression is WRONG whereas, it should be the reverse!
By 'arithmetic' I mean bash can do math like in this expression: $x=$(( $y + 34)).
I use 'arithmetic/no arithmetic' when in mathematical expressions the argument acts as it is expected and 'NO arithmetic' when it misbehaves compared with what it is expected.
As you see, only no 10 and 11 are the problematic ones!
Perfect!
PS: Note that the MOST popular answer fails in case 9!
This is a little rough around the edges but a little more novice friendly.
if [ $number -ge 0 ]
then
echo "Continue with code block"
else
echo "We matched 0 or $number is not a number"
fi
This will cause an error and print "Illegal number:" if $number is not a number but it will not break out of the script. Oddly there is not a test option I could find to just test for an integer. The logic here will match any number that is greater than or equal to 0.