5644
Comment: added shopt -s extglob
|
4174
Demonstrating how to combine cases in "case"
|
Deletions are marked like this. | Additions are marked like this. |
Line 1: | Line 1: |
[[Anchor(faq54)]] | <<Anchor(faq54)>> |
Line 3: | Line 3: |
First, you have to define what you mean by "number". The most common case when people ask this seems to be "a non-negative integer, with no leading + sign". Or in other words, a string of all digits. Other times, people want to validate a floating-point input, with optional sign and optional decimal point. | |
Line 4: | Line 5: |
- first the real bash answer [by xmb] - | === Hand parsing === If you're validating a simple "string of digits", you can do it with a [[glob]]: |
Line 7: | Line 9: |
shopt -s extglob [[ $var == @([0-9]) ]] |
# Bash if [[ $foo != *[!0-9]* ]]; then echo "'$foo' is strictly numeric" else echo "'$foo' has a non-digit somewhere in it" fi }}} The same thing can be done in Korn and POSIX shells as well, using {{{case}}}: {{{ # POSIX case $var in '') printf 'var is empty\n';; *[!0-9]*) printf '%s has a non-digit somewhere in it\n' "$var";; *) printf '%s is strictly numeric\n' "$var";; esac >&2 }}} Of course, if all you care about is vald vs. invalid, you can combine cases: {{{ # POSIX case $var in '' | *[!0-9]*) echo "$0: $var: invalid digit" >&2; exit 1;; esac }}} If you need to allow a leading negative sign, or if want a valid floating-point number or something else more complex, then there are a few possible ways. Standard globs aren't expressive enough to do this, but you can trim off any sign and then compare: {{{ # POSIX case ${var#[-+]} in # notice ${var#prefix} substitution to trim sign '') printf 'var is empty\n';; *.*.*) printf '%s has more than one decimal point in it\n' "$var";; *[!0-9.]*) printf '%s has a non-digit somewhere in it\n' "$var";; *) printf '%s looks like a valid float\n' "$var";; esac >&2 |
Line 11: | Line 54: |
--- rest is crap that was on the faq before - First, you have to define what you mean by "number". The most common case seems to be that, when people ask this, they actually mean "a non-negative integer, with no leading + sign". Or in other words, a string of all digits. |
Or in Bash, we can use [[glob|extended globs]]: |
Line 16: | Line 57: |
if [[ $foo = *[^0-9]* ]]; then echo "'$foo' has a non-digit somewhere in it" else echo "'$foo' is strictly numeric" |
# Bash -- extended globs must be enabled explicitly in versions prior to 4.1. # Check whether the variable is all digits. shopt -s extglob [[ $var = +([0-9]) ]] }}} A more complex case: {{{ # Bash / ksh shopt -s extglob if [[ $foo = @(*[0-9]*|!([+-]|)) && $foo = ?([+-])*([0-9])?(.*([0-9])) ]]; then echo 'foo is a floating-point number' |
Line 23: | Line 74: |
This can be done in Korn and legacy Bourne shells as well, using {{{case}}}: | Optionally, `case..esac` may have been used in shells with extended pattern matching. The leading test of {{{$foo}}} is to ensure that it contains at least one digit, isn't empty, and contains more than just + or - by itself. If your definition of "a valid number" is even more complex, or if you need a solution that works in legacy Bourne shells, you might prefer to use an external tool's [[RegularExpression|regular expression]] syntax. Here is a portable version (explained in detail [[http://www.wplug.org/wiki/Meeting-20100612#EXERCISE_TWO|here]]), using {{{egrep}}}: |
Line 26: | Line 79: |
case "$foo" in *[!0-9]*) echo "'$foo' has a non-digit somewhere in it" ;; *) echo "'$foo' is strictly numeric" ;; esac |
# Bourne if echo "$foo" | grep -qE '^[-+]?([0-9]+\.?|[0-9]*\.[0-9]+)$'; then echo "'$foo' is a number" else echo "'$foo' is not a number" fi |
Line 31: | Line 86: |
If what you actually mean is "a valid floating-point number" or something else more complex, then there are a few possible ways. One of them is to use Bash's {{{extglob}}} capability: |
Bash version 3 and above have regular expression support in the [[ command. |
Line 36: | Line 89: |
# Bash example; extended globs are disabled by default shopt -s extglob [[ $foo = *[0-9]* && $foo = ?([+-])*([0-9])?(.*([0-9])) ]] && echo "foo is a number" }}} |
# Bash # The regexp must be stored in a var and expanded for backward compatibility with versions < 3.2 |
Line 41: | Line 92: |
The leading test of {{{$foo}}} is to ensure that it contains at least one digit. The extended glob, by itself, would match the empty string, or a lone {{{+}}} or {{{-}}}, which may not be desirable behavior. The features enabled with {{{extglob}}} in Bash are also allowed in the Korn shell by default. The difference here is that Ksh lacks Bash's {{{[[}}} and must use {{{case}}} instead: {{{ # Ksh example using extended globs case $foo in *[0-9]*) case $foo in ?([+-])*([0-9])?(.*([0-9]))) echo "foo is a number";; esac;; esac }}} Note that this uses the same extended glob as the Bash example before it; the third closing parenthesis at the end of it is actually part of the {{{case}}} syntax. If your definition of "a valid number" is even more complex, or if you need a solution that works in legacy Bourne shells, you might prefer to use a regular expression. Here is a portable version, using {{{egrep}}}: {{{ if test "$foo" && echo "$foo" | egrep '^[-+]?[0-9]*(\.[0-9]*)?$' >/dev/null; then echo "'$foo' might be a number" else echo "'$foo' might not be a number" fi }}} (Like the extended globs, this extended regular expression matches a lone {{{+}}} or {{{-}}}, and the code may therefore require adjustment. The initial {{{test}}} command only requires a non-empty string. Closing the last "bug" is left as an exercise for the reader, mostly because GreyCat is too damned lazy to learn {{{expr(1)}}}.) Bash version 3 and above have regular expression support in the [[ command. However, due to serious bugs and syntax changes in Bash's [[ regex support, we '''do not recommend''' using it. Nevertheless, if I simply omit all Bash regex answers here, someone will come along and fill them in -- and they probably won't work, or won't contain all the caveats necessary. So, in the interest of preventing disasters, here are the Bash regex answers that you should not use. {{{ if [[ $foo = *[0-9]* && $foo =~ ^[-+]?[0-9]*\(\.[0-9]*\)?$ ]]; then # Bash 3.1 only! |
regexp='^[-+]?[0-9]*(\.[0-9]*)?$' if [[ $foo = *[0-9]* && $foo =~ $regexp ]]; then |
Line 79: | Line 100: |
Unfortunately, Bash changed the syntax of its regular expression support after version 3.1, so the following ''may'' work in some patched versions of Bash 3.2: | === Using the parsing done by [ and printf (or "using eq") === {{{ # fails with ksh if [ "$foo" -eq "$foo" ] 2>/dev/null; then echo "$foo is an integer" fi }}} `[` parses the variable and interprets it as an integer because of the `-eq`. If the parsing succeeds the test is trivially true; if it fails `[` prints an error message that `2>/dev/null` hides and sets a status different from 0. However this method fails if the shell is ksh, because ksh evaluates the variable as an arithmetic expression. You can use a similar trick with `printf`, but this won't work in all shells either: |
Line 82: | Line 112: |
if [[ $foo = *[0-9]* && $foo =~ ^[-+]?[0-9]*(\.[0-9]*)?$ ]]; then # **PATCHED** Bash 3.2 only! echo "'$foo' looks rather like a number" else echo "'$foo' doesn't look particularly numeric to me" |
# BASH if printf %f "$foo" >/dev/null 2>&1; then echo "$foo is a float" |
Line 89: | Line 118: |
It fails rather spectacularly in bash 3.1 and in bash 3.2 without patches. Note that the parentheses in the {{{egrep}}} regular expression and the bash 3.2.patched regular expression don't require backslashes in front of them, whereas the ones in the bash 3.1 command do. Stuffing the Bash regex into a variable, and then using {{{[[ $foo =~ $bar ]]}}}, may also be an effective workaround in some cases. But this belongs in a separate FAQ.... If you just want to guarantee ahead of time that a variable contains an integer, without actually checking, you can give the variable the "integer" attribute. {{{ declare -i foo foo=-10+1; echo "$foo" #prints -9 foo="hello"; echo "$foo" # the value of the variable "hello" is evaluated, if unset foo is assigned 0 foo="Some random string" #result in an error. }}} Any value assigned to a variable with the integer attribute set is evaluated as an [:ArithmeticExpression:arithmetic expression] just like inside `$(( ))`. Bash will raise an error if you try to assign an invalid arithmetic expression. In Bash and ksh93, if a variable which has been declared integer is used in a `read` command, and the user types an invalid integer variable, the variable will have a value of 0 instead of whatever the user typed. `read` will give no other indication of a problem. {{{ # bash (and ksh93, if you replace declare with typeset) declare -i foo read foo # user types hello; bash doesn't complain, and read returns success echo $foo # prints 0 }}} In the Korn shell (ksh88), if a variable is declared integer and used in a `read` command, and the user types an invalid integer, the shell complains, the read command returns an error status, and the value of the variable is unchanged. {{{ # ksh88 $ typeset -i foo $ foo=42 $ read foo hello ksh: hello: bad number $ echo $? 1 $ echo $foo 42 }}} |
You can use `%d` to parse an integer. Take care that the parsing might be (is supposed to be?) [[locale]]-dependent. |
How can I tell whether a variable contains a valid number?
First, you have to define what you mean by "number". The most common case when people ask this seems to be "a non-negative integer, with no leading + sign". Or in other words, a string of all digits. Other times, people want to validate a floating-point input, with optional sign and optional decimal point.
Hand parsing
If you're validating a simple "string of digits", you can do it with a glob:
# Bash if [[ $foo != *[!0-9]* ]]; then echo "'$foo' is strictly numeric" else echo "'$foo' has a non-digit somewhere in it" fi
The same thing can be done in Korn and POSIX shells as well, using case:
# POSIX case $var in '') printf 'var is empty\n';; *[!0-9]*) printf '%s has a non-digit somewhere in it\n' "$var";; *) printf '%s is strictly numeric\n' "$var";; esac >&2
Of course, if all you care about is vald vs. invalid, you can combine cases:
# POSIX case $var in '' | *[!0-9]*) echo "$0: $var: invalid digit" >&2; exit 1;; esac
If you need to allow a leading negative sign, or if want a valid floating-point number or something else more complex, then there are a few possible ways. Standard globs aren't expressive enough to do this, but you can trim off any sign and then compare:
# POSIX case ${var#[-+]} in # notice ${var#prefix} substitution to trim sign '') printf 'var is empty\n';; *.*.*) printf '%s has more than one decimal point in it\n' "$var";; *[!0-9.]*) printf '%s has a non-digit somewhere in it\n' "$var";; *) printf '%s looks like a valid float\n' "$var";; esac >&2
Or in Bash, we can use extended globs:
# Bash -- extended globs must be enabled explicitly in versions prior to 4.1. # Check whether the variable is all digits. shopt -s extglob [[ $var = +([0-9]) ]]
A more complex case:
# Bash / ksh shopt -s extglob if [[ $foo = @(*[0-9]*|!([+-]|)) && $foo = ?([+-])*([0-9])?(.*([0-9])) ]]; then echo 'foo is a floating-point number' fi
Optionally, case..esac may have been used in shells with extended pattern matching. The leading test of $foo is to ensure that it contains at least one digit, isn't empty, and contains more than just + or - by itself.
If your definition of "a valid number" is even more complex, or if you need a solution that works in legacy Bourne shells, you might prefer to use an external tool's regular expression syntax. Here is a portable version (explained in detail here), using egrep:
# Bourne if echo "$foo" | grep -qE '^[-+]?([0-9]+\.?|[0-9]*\.[0-9]+)$'; then echo "'$foo' is a number" else echo "'$foo' is not a number" fi
Bash version 3 and above have regular expression support in the [[ command.
# Bash # The regexp must be stored in a var and expanded for backward compatibility with versions < 3.2 regexp='^[-+]?[0-9]*(\.[0-9]*)?$' if [[ $foo = *[0-9]* && $foo =~ $regexp ]]; then echo "'$foo' looks rather like a number" else echo "'$foo' doesn't look particularly numeric to me" fi
Using the parsing done by [ and printf (or "using eq")
# fails with ksh if [ "$foo" -eq "$foo" ] 2>/dev/null; then echo "$foo is an integer" fi
[ parses the variable and interprets it as an integer because of the -eq. If the parsing succeeds the test is trivially true; if it fails [ prints an error message that 2>/dev/null hides and sets a status different from 0. However this method fails if the shell is ksh, because ksh evaluates the variable as an arithmetic expression.
You can use a similar trick with printf, but this won't work in all shells either:
# BASH if printf %f "$foo" >/dev/null 2>&1; then echo "$foo is a float" fi
You can use %d to parse an integer. Take care that the parsing might be (is supposed to be?) locale-dependent.