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]+)?$
^-*[0-9]+([.][0-9]+)?$
to also test for negative numbers –
Exalted ^-?
rather than ^-*
unless you're actually doing the work to handle multiple inversions correctly. –
Benfield ^[0-9]*([.][0-9]+)?$
This would require an additional check for a non-empty string tough.. –
Lorola if ! [[ -n "$yournumber" && "$yournumber" =~ ^-?[0-9]*([.][0-9]+)?$ ]]; then echo NAN; fi
–
Lorola [[ $yournumber =~ ^[0-9]+$ ]]
. –
Langrage =~
changed between 3.1 and 3.2, whereas backslash handling in assignments is constant in all relevant releases of bash. Thus, following the practice of always assigning regular expressions to variables before matching against them using =~
avoids surprises. I do it here to teach good habits, even though this particular regex has no backslash escapes. –
Benfield =~
started at 3.0 and has had varying implementations since then. What's actually most difficult in it is that you would still need to quote characters in values of variables you intended to provide as literal (e.g. ${VAR//./\\.}
) to prevent them from being parsed as regex characters whereas with ==
you could just place them in ""
e.g. +(x)"$V"
. And the only reason why I could use it over ext. patterns is when I could make use of BASH_REMATCH
. –
Langrage [[ $s =~ $re_start"$literal"$re_end ]]
, for instance, treats the expanded contents of "$literal"
as, well, literal, no escaping needed. (I also find [.]
considerably more readable and otherwise easier to handle than \.
, but that's admittedly bikeshedding) –
Benfield [[ $s =~ $re_start"$literal"$re_end ]]
would work just like the way of ==
starting 3.2, but not on 3.0 or 3.1. [[ $s =~ (x) ]]
without needing to save on a variable would also only work starting 4.0. Extended patterns need only to be enabled once at the start of the script so it wouldn't really matter. It's also compatible to all versions of bash starting 2.05b at least. Well these last comments of mine were already just about why someone would prefer extended patterns over =~
. The info could also be helpful to other readers as well perhaps if they care compatibility. –
Langrage /bin/sh
is POSIX sh (or, on ancient systems, Bourne shell), not bash, even when it's a symlink to bash. –
Benfield if [[ 123 =~ '^[0-9]+$' ]]; then echo good; fi
and got nothing. But re='^[0-9]+$'; if [[ 123 =~ $re ]]; then echo good; fi
said good
. Why? Do I have to escape something in the first version? –
Viscometer =~
in quotes makes it no longer a regular expression but a regular string (in modern versions of bash but not some ancient ones), whereas the version I gave in this answer works consistently in every release where =~
is supported at all. –
Benfield .5
? I would use ^-?([0-9]*[.])?[0-9]+$
instead. –
Markova if
. See BashFAQ #31 (mywiki.wooledge.org/BashFAQ/031) for the rest; beyond what's covered there, !
inverts the exit status of a command (make truthy things falsey, and falsey things true); it's a common way of spelling "not" in many, many languages. –
Benfield str="hello world"; re='[[:space:]]'; if [[ $str =~ $re ]]; then echo "matched"; fi
behaving as-expected at ideone.com/K7kM6M –
Benfield eval
or some other pre-execution stage. Can you come up with an example that doesn't work without the quotes and post a link to it executing on ideone.com? –
Benfield read
fails for the last line entered into the stdin box (which can be cured by putting an empty line on the end of the stdin box). Other than that, though, I haven't historically had a problem. –
Benfield .3
, or ending with a dot like 3.
, it will NOT work. I would like to suggest using ^[+-]?([0-9]*[.])?([0-9]+)?$
, however, three special cases: .
, +.
, -.
have to be excluded first. –
Endometriosis if [[ ! $yournumber =~ $re ]]
? –
Hecate !
is not in the square brackets in your code snippet. –
Hecate [[ ! $foo && $bar ]]
; a reader needs to think about whether the !
is applied only to $foo
, or to the result of ANDing both; put it in the outside, and it's obvious at a glance that the negation applies to the entire test, not just one branch of it. –
Benfield ^[1-9][0-9]*$
–
Howlyn return
instead of exit 1
, which exits the ssh session as well –
Titrate return
. –
Benfield printf
is MUCH MORE efficient. –
Spit printf
to something else; setting up a pipeline is not efficient at all. –
Benfield 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 (?*-*
/ *.*.*
)
if test ...
–
Annoying ${string#-}
(which doesn't work in antique Bourne shells, but works in any POSIX shell) to accept negative integers. –
Faye '.' | *.*.*
to the disallowed patterns, and add dot to the allowed characters. Similarly, you can allow an optional sign before, although then I would prefer case ${string#[-+]}
to simply ignore the sign. –
Annoying case "$string" in
instead of case $string in
? –
Snavely The following solution can also be used in basic shells such as Bourne without the need for regular expressions. Basically any numeric value evaluation operations using non-numbers will result in an error which will be implicitly considered as false in shell:
"$var" -eq "$var"
as in:
#!/bin/bash
var=a
if [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null; then
echo number
else
echo not a number
fi
You can can also test for $? the return code of the operation which is more explicit:
[ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null
if [ $? -ne 0 ]; then
echo $var is not number
fi
Redirection of standard error is there to hide the "integer expression expected" message that bash prints out in case we do not have a number.
CAVEATS (thanks to the comments below):
[[ ]]
instead of [ ]
will always evaluate to true
true
bash: [[: 1 a: syntax error in expression (error token is "a")
bash: [[: i: expression recursion level exceeded (error token is "i")
[[ a -eq a ]]
evaluates to true (both arguments get converted to zero) –
Centuplicate bash
(which was requested) and most other shells, but sadly not in ksh
. If you want to be portable, use jilles' solution. –
Longshoreman if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
–
Machismo [
builtin will evaluate the arguments as arithmetic. That is true in both ksh93 and mksh. Further, since both of those support arrays, there is easy opportunity for code injection. Use a pattern match instead. –
Voodoo [[ ]]
but not for [ ]
. That said, this behavior is unspecified by both the POSIX standard for test
and in bash's own documentation; future versions of bash could modify behavior to match ksh without breaking any documented behavioral promises, so relying on its current behavior persisting is not guaranteed to be safe. –
Benfield man test
shows -eq
is for ints and =
is for strings. the man page implies but doesn't explicitly say -eq
will fail for string comparison. –
Clung ! [ "$var" -eq "$var" ]
. Reason: -ne
returns non-zero for non-integer arguments. –
Stilbestrol $var
is non-null. I'll suggest an edit: [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null
–
Stilbestrol [ … ]
form (which is test
) POSIX quite clearly states that this is: "n1 -eq n2: True if the integers n1 and n2 are algebraically equal; otherwise, false." (pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html) Strings cannot be integers or algebraically equal, so the worst than can happen is that it gives an exit status >1
instead of just 1
which is however not an issue for the above problem. –
Dilapidation [ -n "$var" ]
? v='' ; [ "$v" -eq "$v" ]
returns false (return code = 2). –
Carsoncarstensz v=99999999999999999999999999999 ; [ "$v" -eq "$v" ] 2> /dev/null ; echo $?
prints 2. On the other hand, v=999999999999999999999999999999 ; expr 1 + "$v" \* "$v" 2> /dev/null ; echo $?
works well and prints 0. Which one is better depends if you need to check if it is within the integer range. –
Carsoncarstensz 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"
shopt -s extglob
from your post (that I upvoted, it's one of my favorite answers here), since in Conditional Constructs you can read: When the ==
and !=
operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob
shell option were enabled. I hope you don't mind! –
Barehanded shopt extglob
... that's a good thing to know! –
Barehanded 3.2.25(1)-release
of bash: -bash: syntax error in conditional expression: unexpected token
(' -bash: syntax error near `?(-'. In that release, the featured noted by @Barehanded is not working. –
Houppelande [[ x3x = *[[:digit:]]* ]]
, even with the single =
operator. In 3.2 bash man pages the string as if the extglob shell option were enabled
is not found. –
Houppelande ?(-)
bit that optionally matches the leading hyphen, so you just match digits with +([0-9])
–
Sisterinlaw bash 5.0.11
, but shouldn't it be "$1"
instead of $1
? or doesn't matter in this case? –
Hovey [[...]]
are not subject to word splitting or glob expansion. –
Sisterinlaw [[...]]
in the manual (or help [[
at a bash prompt): only the right-hand side of ==
is a pattern. –
Sisterinlaw [[:digit:]]
instead of [:digit:]
for POSIX. –
Cutty syntax error near unexpected token
('` –
Thimerosal regex
for this is something overkill. –
Songer There are some strongly different methods regarding different kinds of tests.
I reviewed most relevant methods and built this comparison. Mostly:
case
switches,is_uint()
These functions implement code to assess whether an expression is an unsigned integer, i.e. consists entirely of digits.
Using parameter expansion
(glenn jackman's answer based on bash parameter expansion. This was my approach before all this!)
isuint_Parm() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}
Using fork to grep
Proposed by Sergio Abreu's answer
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 even:
isuint_Bash() { set -- "${1//[+-]/.}";(( 10#$1 >= 0 )) 2>/dev/null ;}
But don't use this!!! Thanks to gniourf_gniourf's comment for driving me to this!
Using case
jilles's answer based on case
switches
isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
Using bash's regex
Charles Duffy's answer, based on bash 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
Something like Alberto Zaccagni's answer based on integer:
isint_Shell() { [ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null;}
Or using bashisms:
isint_Bash() { set -- "${1//[!+-]}" ${1#${1//[!+-]}};
(( ( 0 ${1:-+} 10#$2 ) ? 1:1 )) 2>/dev/null ;}
Using case
isint_Case() { case ${1#[-+]} in ''|*[!0-9]*) return 1;;esac;}
Using 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=(
0 1 42 -3 +42 +3. .9 3.14 +3.141 -31.4 '' . 3-3 3.1.4 3a a3 blah 'Good day!'
);printf '%-12s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s\n' \
Value\\Func U{Prm,Grp,Bsh,Cse,Rgx} I{Prm,Shl,Bsh,Cse,Rgx} N{Prm,Cse,Rgx};\
for var in "${testcases[@]}";do
outstr='';
for func in isuint_{Parm,Grep,Bash,Case,Regx} \
isint_{Parm,Shell,Bash,Case,Regx} isnum_{Parm,Case,Regx};do
if $func "$var"; then
outstr+=' ##'
else
outstr+=' --'
fi
done
printf '%-11s %s\n' "$var" "$outstr"
done
Should output:
Value\Func UPrm UGrp UBsh UCse URgx IPrm IShl IBsh ICse IRgx NPrm NCse NRgx
0 ## ## ## ## ## ## ## ## ## ## ## ## ##
1 ## ## ## ## ## ## ## ## ## ## ## ## ##
42 ## ## ## ## ## ## ## ## ## ## ## ## ##
-3 -- -- -- -- -- ## ## ## ## ## ## ## ##
+42 -- -- -- -- -- ## ## ## ## ## ## ## ##
+3. -- -- -- -- -- -- -- -- -- -- ## ## ##
.9 -- -- -- -- -- -- -- -- -- -- ## ## ##
3.14 -- -- -- -- -- -- -- -- -- -- ## ## ##
+3.141 -- -- -- -- -- -- -- -- -- -- ## ## ##
-31.4 -- -- -- -- -- -- -- -- -- -- ## ## ##
-- -- -- -- -- -- -- -- -- -- -- -- --
. -- -- -- -- -- -- -- -- -- -- -- -- --
3-3 -- -- -- -- -- -- -- ## -- -- -- -- --
3.1.4 -- -- -- -- -- -- -- -- -- -- -- -- --
3a -- -- -- -- -- -- -- -- -- -- -- -- --
a3 -- -- -- -- -- -- -- -- -- -- -- -- --
blah -- -- -- -- -- -- -- -- -- -- -- -- --
Good day! -- -- -- -- -- -- -- -- -- -- -- -- --
I hope!
Note: uint_bash
seems not perfect! In fine: second gniourf_gniourf's comment rightly said:
All methods using arithmetic context are subject to arbitrary code injection and are very dangerous! All the *_Bash functions here are dangerous and should be marked as ANTI=PATTERNS and "DANGEROUS: SUBJECT TO ARBITRARY CODE INJECTION. SHOULD NOT BE USED UNDER ANY CIRCUMPSTANCES".
Then I've built this test function:
testFunc() {
local tests=1000 start=${EPOCHREALTIME//.}
for ((;tests--;)) ;do
"$1" "$3"
done
printf -v "$2" %u $((${EPOCHREALTIME//.}-start))
}
percent(){ local p=00$((${1}00000/$2));printf -v "$3" %.2f%% ${p::-3}.${p: -3};}
sortedTests() {
local func NaNTime NumTime ftyp="$1" nTest="$2" tTest="$3" min i pct line
local -a order=()
shift 3
for func ;do
testFunc "${ftyp}_$func" NaNTime "$tTest"
testFunc "${ftyp}_$func" NumTime "$nTest"
order[NaNTime+NumTime]=${ftyp}_$func\ $NumTime\ $NaNTime
done
printf '%-12s %11s %11s %14s\n' Function Number NaN Total
min="${!order[*]}" min=${min%% *}
for i in "${!order[@]}";do
read -ra line <<<"${order[i]}"
percent "$i" "$min" pct
printf '%-12s %9d\U00B5s %9d\U00B5s %12d\U00B5s %9s\n' \
"${line[@]}" "$i" "$pct"
done
}
I could run in this way:
sortedTests isuint "This is not a number." 31415926535897932384 \
Case Grep Parm Bash Regx ;\
sortedTests isint "This is not a number." 31415926535897932384 \
Case Parm Shell Bash Regx ;\
sortedTests isnum "This string is clearly not a number..." \
3.141592653589793238462643383279502884 Case Parm Regx
On my host, this shows somthing like:
Function Number NaN Total
isuint_Case 1763µs 1535µs 3298µs 100.00%
isuint_Parm 2571µs 3319µs 5890µs 178.59%
isuint_Regx 5014µs 5952µs 10966µs 332.50%
isuint_Bash 6379µs 6106µs 12485µs 378.56%
isuint_Grep 293347µs 287733µs 581080µs 17619.16%
Function Number NaN Total
isint_Case 2020µs 1938µs 3958µs 100.00%
isint_Parm 3291µs 4707µs 7998µs 202.07%
isint_Shell 5775µs 5183µs 10958µs 276.86%
isint_Regx 5371µs 5836µs 11207µs 283.15%
isint_Bash 8946µs 7718µs 16664µs 421.02%
Function Number NaN Total
isnum_Case 2181µs 2232µs 4413µs 100.00%
isnum_Parm 4502µs 6017µs 10519µs 238.36%
isnum_Regx 8247µs 13019µs 21266µs 481.89%
You could download the full isnum comparison script here or the full isnum comparison script as text here (with UTF8 and LATIN handling).
case
way is clearly the quickest! About 3x quicker than regex and 2x quicker than using parameter expansion.grep
or any binaries) should be avoided when not needed, mostly for small strings, a single line, or other one shot operations.The case
method has become my favored choice:
is_uint() { case $1 in '' | *[!0-9]* ) return 1;; esac ;}
is_int() { case ${1#[-+]} in '' | *[!0-9]* ) return 1;; esac ;}
is_unum() { case $1 in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}
is_num() { case ${1#[-+]} in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}
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).
set --
can be used to set positional params –
Bridgman casenum
don't work with floating numbers! And his test don't use his $fun
variable!! But I agree, I could pay some attention to this way of doing! –
Songer case
case (sic) is now clearly faster on IdeOne too. (I should have checked ...) Though probably a better test would not only check the success flow. –
Annoying isnum(){ case ${1#[-+]} in ''|*[!0-9.]*|*.*.*) return -1;;esac ;}
... seem good! –
Songer isuint_Bash
is slightly faster than case
in the NaN
test. If you are in a tight loop where every cycle counts, and you expect mostly invalid input, that could tilt the comparison in favor of the parameter expansion version. But only for the uint
case. Perhaps the difference is small enough that it's within the margin of measurement error anyway. –
Annoying +Numberr
column, I won't try to explain this there;) –
Songer is_float() { is_num "${1/[eE][-+]/}"; }
–
Ulla 1 && 1
, or more evil: 1 && a[$(touch I_JUST_TOUCHED_A_FILE_LOL)]
. Please remove this antipattern from your answer! –
Barehanded data='1 && a[$(touch I_JUST_TOUCHED_A_FILE_LOL)]';case $data in JOB ) echo job;; *JUST* ) echo tout juste;;*) echo other;;esac;
don't see any kind of problem! ... ( case
!= eval
:-) –
Songer isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
and then isuint_Bash '1 && 1' && echo "Is a number"'
so according to this, 1 && 1
is a number, and worse: isuint_Bash '1 && a[$(touch I_JUST_TOUCHED_A_FILE_LOL)]' && echo "Is a number"
will say it's a number and touch a file :( –
Barehanded case
method it is a bash integer method!! The case method, with 4 test functions at bottom of this answer are really robusts and notably quicker the using regex. –
Songer case
method is very good! just wanted to point out the code injection issue when using the arithmetic context method. All your methods using arithmetic context are subject to arbitrary code injection and are very dangerous! all the *_Bash
functions you gave are dangerous and should be marked as ANTI-PATTERNS and "DANGEROUS: SUBJECT TO ARBITRARY CODE INJECTION. SHOULD NOT BE USED UNDER ANY CIRCUMPSTANCES". Please do another edit to clearly mention this security issue for all the _Bash
functions! –
Barehanded 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.
*[!0-9]*
is a pattern that matches all strings with at least 1 non-digit character. ${num##*[!0-9]*}
is a "parameter expansion" where we take the content of the num
variable and remove the longest string that matches the pattern. If the result of the parameter expansion is not empty (! [ -z ${...} ]
) then it's a number since it does not contain any non-digit character. –
Backboard 122s
:-(
–
Erick 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.
isnumber(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; }
–
Cutch isnumber 23 && echo "this is a number" || echo "not a number"
–
Christianity 2>/dev/null
, so that isnumber "foo"
does not pollute stderr? –
Hemihedral isnumber "'a"
will return true. This is documented in the POSIX spec where you'll read: If the leading character is a single-quote or double-quote, the value shall be the numeric value in the underlying codeset of the character following the single-quote or double-quote. –
Barehanded :D
. –
Barehanded isinteger() { [[ $1 ]] && printf '%d' "$1" >/dev/null 2>&1; }
will fail with, e.g., isinteger 09
: that's because of a silly leading 0
. Now you might argue that 09
shouldn't be validated... whatever, isnumber "'a"
is an already good proof that all this design is broken. –
Barehanded sed "s/^0*//; s/'/^/"
–
Supplementary isnumber
(the isinteger
function I gave is just a (failed) attempt in generalizing your method). Funny, but now 0
is not a number. Your function now spawns 4 subshells, uses an external command, doesn't work. And you claim that a shell is just a DSL for controlling files and processes? you need to learn better shell techniques, and when you're more knowledgeable you'll see that the only sane way of solving this problem is to indeed parse the string. –
Barehanded printf
. For example, your function validates 42\n\n\n\n\n\n\n\n\n\n\n\n\n
or %s42%s
or %d9
–
Barehanded $'1\n\n\n'
; that's because $(...)
trims trailing newlines. Whatever. You're at the point where you criticize methods that parse the string explicitly (and so we expect your method to be really superior) yet: your method spawns 3 subshells, isn't even 100% safe (regarding trailling newlines), somehow parses the string with sed
, is much less efficient than methods that directly parse the string. Good job. –
Barehanded s/'/^/
. Would you mind precising it ? I don't see how replacing '
with ^
would make any sense (this is what sed
does when ^
is alone) when considering a decimal input number. –
Melodeemelodeon 'blah
as a number, so tweak it to something that printf will reject –
Supplementary '10
as a number either? '
s can legitimately be present as syntax in code that encodes literals meant to be treated as numbers, but they should never be present in the data. –
Benfield 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
'BEGIN { exit(1-(a==a+0)) }'
is slightly hard to grok but can be used in a function which returns true or false just like [
, grep -q
, etc. –
Annoying 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
man -P 'less +/parameter\/' bash
part. Learning something new every day. :) –
Mutineer \-
in regular expression to address the issue. Use [0-9\-\.\+]
to account for floats and signed numbers. –
Worlock echo $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' && echo yes || echo no
–
Worlock 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")
foo=1;set -- foo;(( $1 )) 2>/dev/null && echo "'$1' is a number"
–
Songer 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.
var
. –
Barehanded $'0\n\n\n1\n\n\n2\n\n\n3\n'
. –
Barehanded 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]+$'
.
[-]
instead of \-
and [.]
instead of \.
is a little more verbose, but it means your strings don't have to change if they're used in a context where backslashes get consumed). –
Benfield if [[ $yournumber =~ ^[0-9]+([.][0-9]+)?$ ]] ; then
in an old Ubuntu 14.04 based system but, somehow, it stopped working after upgrading to Ubuntu 20.04, your first solution for "Test Integer" does the same in 20.04. I can't say if it is related to the upgrade or maybe my script was wrong in first instance and -somehow- yet working in the old system. Thank you very much. –
Este #!/bin/sh
? If so, it should still work in modern Ubuntu as long as you use a #!/bin/bash
shebang, and avoid starting scripts with sh scriptname
(which ignores the shebang and forces use of sh
instead of bash
). –
Benfield [[ $1 =~ ^-?[0-9]+$ ]] && echo "number"
Don't forget -
to include negative numbers!
=~
existed at least as far back as bash 3.0. –
Faye 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.
%f
is probably better anyway) –
Christianity if
itself? That's what if
does... if printf "%g" "$var" &> /dev/null; then ...
–
Gushy 'a
. –
Barehanded 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
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.
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.
expr -- "$number" + 0
; yet this will still pretend that 0 isn't a number
. From man expr
: Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
–
Barehanded expr
. If you are confined to a lesser Bourne shell like POSIX sh
, then maybe. –
Annoying $(( ))
. You're talking 1970s Bourne to need expr
. –
Benfield expr
is not encouraged in Bash? I think this solution is POSIX compliant and the most concise. Yes it fails when number=0
. It can be improved by expr 1 + "$v" \* "$v"
which ensures the answer is not zero. –
Carsoncarstensz expr
made sense for th original Bourne shell because it did not have equivalent or superior features built in; but that is no longer true for modern shells. Again, if you are shooting for POSIX compliance, there may be corner cases where there are no convenient equivalents to expr
, but adding two integers is not one of them. –
Annoying 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
[[ ! -n ${1//[+\-0-9]/} ]] && echo "is a number" || echo "is not a number";
. The problem now is that +-123
will pass too. –
Caffeine [ $var = ${var//[0-9]/}${var/[+\-]/} ]
–
Discommodity 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
[[ $VAR = *[[:digit:]]* ]]
will return true if the variable contains a number, not if it is an integer. –
Sisterinlaw [[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"
prints numeric
. Tested in bash version 3.2.25(1)-release
. –
Houppelande [[ -n $VAR && $VAR != *[^[:digit:]]* ]]
–
Gossip 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; }
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; }
function
keyword as it doesn't do anything useful. Also, I'm not sure about the usefulness of the return values. Unless otherwise specified, the functions will return the exit status of the last command, so you don't need to return
anything yourself. –
Revenue return
s are confusing and make it less readable. Using function
keywords or not is more a question of personal flavor at least i removed them from the one liners to save some space. thx. –
Muniment 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
When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled.
gnu.org/software/bash/manual/bashref.html#index-_005b_005b –
Isometropia 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
UPDATE 2024 (it still doesn't support floating point numbers)
#!/usr/bin/bash
isnumber(){
if [ -z $1 ]; then
echo {empty string} isn\'t number
return 1
elif [ -z "${1##*[!0-9]*}" ]; then
echo $1 isn\'t number
return 1
else
echo $1 is number
return 0
fi
}
isnumber $1;
./isnumtest
or ./isnumtest ""
I get need a number
–
Suitable isnumber "$1" && var=$1 || echo "Need a number: $1" >&2
(see also #673555) –
Annoying local num
to avoid clobbering any global variable with the same name. –
Annoying 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
.
–
Barehanded a=''
and the string that contains a period only a='.'
so your code is a bit broken... –
Barehanded 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
-n
, etc. (because of echo
), and you're accepting variables with trailing newlines (because of $(...)
). And by the way, print
is not a valid shell command. –
Barehanded I tried ultrasawblade's recipe as it seemed the most practical to me, and couldn't make it work. In the end i devised another way though, based as others in parameter substitution, this time with regex replacement:
[[ "${var//*([[:digit:]])}" ]]; && echo "$var is not numeric" || echo "$var is numeric"
It removes every :digit: class character in $var and checks if we are left with an empty string, meaning that the original was only numbers.
What i like about this one is its small footprint and flexibility. In this form it only works for non-delimited, base 10 integers, though surely you can use pattern matching to suit it to other needs.
sed
is POSIX, while your solution is bash
. Both have their uses –
Perspiration I found quite a short version:
function isnum()
{
return `echo "$1" | awk -F"\n" '{print ($0 != $0+0)}'`
}
"0"
? –
Encyclopedist variable to check
number=12345
or number=-23234
or number=23.167
or number=-345.234
check numeric or non-numeric
echo $number | grep -E '^-?[0-9]*\.?[0-9]*$' > /dev/null
decide on further actions based on the exit status of the above
if [ $? -eq 0 ]; then echo "Numeric"; else echo "Non-Numeric"; fi
Following up on David W's answer from Oct '13, if using expr
this might be better
test_var=`expr $am_i_numeric \* 0` >/dev/null 2>&1
if [ "$test_var" = "" ]
then
......
If numeric, multiplied by 1 gives you the same value, (including negative numbers). Otherwise you get null
which you can test for
expr
is a beast which is hard to tame. I have not tested this solution but I would avoid expr
in favor of modern shell built-ins unless compatibility back to legacy shells from the early 1980s is an important requirement. –
Annoying Another variant of implementation.
if_math_expr.sh
#!/bin/bash
# Script both for execution and inclusion.
if [[ -n "$BASH" ]]; then
function if_math_expr()
{
(( "$*" || ! "$*" )) 2> /dev/null && return 0
return 1
}
if [[ -z "$BASH_LINENO" || BASH_LINENO[0] -eq 0 ]]; then
# Script was not included, then execute it.
if_math_expr "$@"
fi
fi
if_int.sh
#!/bin/bash
# Script both for execution and inclusion.
if [[ -n "$BASH" ]]; then
function if_int()
{
local __tmp=''
if (( "$*" || ! "$*" )) 2> /dev/null; then
(( __tmp = "$*" )) 2> /dev/null # assignment with evaluation and deduction
[[ "$__tmp" == "$*" || "+$__tmp" == "$*" ]] && return 0 # test on not deduced expression
fi
return 1
}
if [[ -z "$BASH_LINENO" || BASH_LINENO[0] -eq 0 ]]; then
# Script was not included, then execute it.
if_int "$@"
fi
fi
cast_to_int.sh
#!/bin/bash
# Script both for execution and inclusion.
if [[ -n "$BASH" ]]; then
# casts any not integer and not deduced value to 0, otherwise leave as is
function cast_to_int()
{
local __tmp
local __var
local __value
for __var in "$@"; do
if (( "$__var" || ! "$__var" )) 2> /dev/null; then
if [[ -n "$__var" ]]; then
eval "__value=\"\$$__var\""
else
__value=''
fi
echo "__value=$__value"
if [[ -n "$__value" && "$__value" != "__value" ]]; then # bash issue workaround for variable '_'
__tmp=''
(( __tmp = "$__var" )) 2> /dev/null # assignment with evaluation and deduction
echo "__tmp=$__tmp"
if [[ "$__tmp" != "$__value" && "+$__tmp" != "$__value" ]]; then # test on not deduced expression
(( "$__var" = 0 )) # reset to 0 because deduced expression still is not an integer
fi
else
(( "$__var" = 0 )) # reset to 0 if empty
fi
else
(( "$__var" = 0 )) # reset to 0 because is not a math expression
fi
done
}
if [[ -z "$BASH_LINENO" || BASH_LINENO[0] -eq 0 ]]; then
# Script was not included, then execute it.
cast_to_int "$@"
fi
fi
Tests:
#!/bin/bash
. ./if_math_expr.sh
function call()
{
echo "> $*"
eval "$*"
}
# caution: `_` variable name is reserved
call "_=''"
call "__=''"
call 'a=+0'
call 'b=-1'
call 'c=-0+0'
call 'd=1/1'
call 'e=1/0'
call 'f=1a'
echo ''
call './if_math_expr.sh && echo true'
call './if_math_expr.sh +0 && echo true'
call './if_math_expr.sh -1 && echo true'
call './if_math_expr.sh -0+0 && echo true'
call './if_math_expr.sh 1/1 && echo true'
call './if_math_expr.sh 1/0 && echo true'
call './if_math_expr.sh 1a && echo true'
echo ''
call './if_math_expr.sh _ && echo true'
call './if_math_expr.sh __ && echo true'
call './if_math_expr.sh a && echo true'
call './if_math_expr.sh b && echo true'
call './if_math_expr.sh c && echo true'
call './if_math_expr.sh d && echo true'
call './if_math_expr.sh e && echo true'
call './if_math_expr.sh f && echo true'
echo ''
call './if_int.sh && echo true'
call './if_int.sh +0 && echo true'
call './if_int.sh -1 && echo true'
call './if_int.sh -0+0 && echo true'
call './if_int.sh 1/1 && echo true'
call './if_int.sh 1/0 && echo true'
call './if_int.sh 1a && echo true'
echo ''
call './if_int.sh _ && echo true'
call './if_int.sh __ && echo true'
call './if_int.sh a && echo true'
call './if_int.sh b && echo true'
call './if_int.sh c && echo true'
call './if_int.sh d && echo true'
call './if_int.sh e && echo true'
call './if_int.sh f && echo true'
echo ''
call '. ./cast_to_int.sh && cast_to_int _ __ a b c d e f'
echo ''
echo "_=$_"
echo "__=$__"
echo "a=$a"
echo "b=$b"
echo "c=$c"
echo "d=$d"
echo "e=$e"
echo "f=$f"
Result:
$ ./test.sh
> _=''
> __=''
> a=+0
> b=-1
> c=-0+0
> d=1/1
> e=1/0
> f=1a
> ./if_math_expr.sh && echo true
> ./if_math_expr.sh +0 && echo true
true
> ./if_math_expr.sh -1 && echo true
true
> ./if_math_expr.sh -0+0 && echo true
true
> ./if_math_expr.sh 1/1 && echo true
true
> ./if_math_expr.sh 1/0 && echo true
> ./if_math_expr.sh 1a && echo true
> ./if_math_expr.sh _ && echo true
true
> ./if_math_expr.sh __ && echo true
true
> ./if_math_expr.sh a && echo true
true
> ./if_math_expr.sh b && echo true
true
> ./if_math_expr.sh c && echo true
true
> ./if_math_expr.sh d && echo true
true
> ./if_math_expr.sh e && echo true
true
> ./if_math_expr.sh f && echo true
true
> ./if_int.sh && echo true
> ./if_int.sh +0 && echo true
true
> ./if_int.sh -1 && echo true
true
> ./if_int.sh -0+0 && echo true
> ./if_int.sh 1/1 && echo true
> ./if_int.sh 1/0 && echo true
> ./if_int.sh 1a && echo true
> ./if_int.sh _ && echo true
> ./if_int.sh __ && echo true
> ./if_int.sh a && echo true
> ./if_int.sh b && echo true
> ./if_int.sh c && echo true
> ./if_int.sh d && echo true
> ./if_int.sh e && echo true
> ./if_int.sh f && echo true
> . ./cast_to_int.sh && cast_to_int _ __ a b c d e f
__value=__value
__value=
__value=+0
__tmp=0
__value=-1
__tmp=-1
__value=-0+0
__tmp=0
__value=1/1
__tmp=1
_=
__=0
a=+0
b=-1
c=0
d=0
e=0
f=0
$ bash --version
GNU bash, version 5.2.15(1)-release (x86_64-pc-msys)
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...
1+1
, but rejects some positive integers with leading 0
s (because 08
is an invalid octal constant). –
Faye 0
is not a number, and it is subject to arbitrary code injection, try it: isInteger 'a[$(ls)]'
. Ooops. –
Barehanded $((...))
is unquoted, a numeric IFS=123
will change it. –
Piezoelectricity a=5; set -- a ; echo $(($1+0))
. Where $1
contain a
which is not a number, but answer is 5
! –
Songer 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
echo -e
is going to introduce portability bugs -- on some shells it prints -e
on output (and those shells where it doesn't do so are violating the letter of the POSIX specification for echo
; with the right set of runtime flags set to make it more strictly compliant than usual, this means bash too can print -e
on output when you run echo -e
). –
Benfield [ -z "${n//[0-9]}" ] && echo IS_NUMBER
or
test -z "${n//[0-9]}" && echo IS_NUMBER
or
[[ "$n" =~ ^[0-9]+$ ]] && echo IS_NUMBER
The accepted answer does not work here, I am on MacOS. The following code works:
if [ $(echo "$number" | grep -c '^[0-9]\+$') = 0 ]; then
echo "it is a number"
else
echo "not a number"
fi
1
and not 0
in the test statement. The other problem is if variable number
contains newlines: number=$'42\nthis is not a number, right?'
will be validated. –
Barehanded /bin/sh
-- either via a #!/bin/sh
shebang or with a script started with sh scriptname
-- rather than bash). –
Benfield grep -c
in a command substitution and comparing the result to 0 is a common and extremely convoluted antipattern. See useless use of grep
and the "pretzel logic" subpage. –
Annoying 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!
isInt 'a[$(ls>&3)]' 3>&1
. You're glad I only used ls
as a command (which is executed twice). And your code also claims that a[$(ls>&3)]
is a number. –
Barehanded unset a; isInt a
. –
Barehanded 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.
[[ ]]
, instead of [ ]
, otherwise it fails on non-integer strings. –
Encyclopedist -ge
is greater than or equal, so it shouldn't miss 0, but it would miss negative numbers. The above solution also works better, since [[ ]]
would always result in a number for @defim's answer, but works nicely for [ ]
since it would generate an error and evaluate to false. –
Blindfold [ "$number" -eq "$number" 2>/dev/null ] && echo isNumber || echo noNumber
–
Hypogenous Below is a Script written by me and used for a script integration with Nagios and it is working properly till now
#!/bin/bash
# Script to test variable is numeric or not
# Shirish Shukla
# Pass arg1 as number
a1=$1
a=$(echo $a1|awk '{if($1 > 0) print $1; else print $1"*-1"}')
b=$(echo "scale=2;$a/$a + 1" | bc -l 2>/dev/null)
if [[ $b > 1 ]]
then
echo "$1 is Numeric"
else
echo "$1 is Non Numeric"
fi
EG:
# sh isnumsks.sh "-22.22"
-22.22 is Numeric
# sh isnumsks.sh "22.22"
22.22 is Numeric
# sh isnumsks.sh "shirish22.22"
shirish22.22 is Non Numeric
echo "$a1"
, otherwise wildcards in the string are expanded and the outcome depends on what files are in the current directory (try isnumsks.sh "*"
, then try again after creating a file called 42
). You're only looking at the first whitespace-delimited word, so 42 psych
is misclassified as numeric. All kinds of input that are not numeric but valid bc
syntax will screw this up, e.g. 2012-01-03
. –
Faye © 2022 - 2024 — McMap. All rights reserved.
test && echo "foo" && exit 0 || echo "bar" && exit 1
approach you're using may have some unintended side effects -- if the echo fails (perhaps output is to a closed FD), theexit 0
will be skipped, and the code will then try toecho "bar"
. If it fails at that too, the&&
condition will fail, and it won't even executeexit 1
! Using actualif
statements rather than&&
/||
is less prone to unexpected side effects. – Benfield[[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }
The curly brackets indicate that things should NOT be executed in a subshell (which would definitely be that way with()
parentheses used instead). Caveat: Never miss the final semicolon. Otherwise you might causebash
to print out the ugliest (and most pointless) error messages... – Quota[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
– Masseuse((5a))
givesbash: ((: 5a: value too great for base (error token is "5a")
– Decemvirate{ VAR="5e" ; (( VAR )) 2>/dev/null; echo $?; }
– Purl