Differences between revisions 10 and 44 (spanning 34 versions)
Revision 10 as of 2008-03-06 04:59:38
Size: 4676
Editor: pgas
Comment: move the declare -i stuff down as it is a bit different, change the examples
Revision 44 as of 2015-02-16 13:01:43
Size: 4174
Editor: TripleEe
Comment: 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, 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. === Hand parsing ===
If you're validating a simple "string of digits", you can do it with a [[glob]]:
Line 7: Line 9:
if [[ $foo = *[^0-9]* ]]; then # Bash
if [[ $foo != *[!0-9]* ]]; then
    echo "'$foo' is strictly numeric"
else
Line 9: Line 14:
else
    echo "'$foo' is strictly numeric"
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 [[glob|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'
Line 14: 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 17: 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 22: 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 27: 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 32: 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 70: 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 73: 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 80: 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 arithmetic expression just like inside $(( )) (see [ArithmeticExpression]). Bash will raise an error if you try to assign an invalid arithmetic expression.
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.

BashFAQ/054 (last edited 2022-08-01 10:02:57 by 89)