
首先,需要明确“数字”的定义。通常情况下,人们询问这个问题时,指的是“非负整数,不带前导加号符号”。换句话说,就是由数字组成的字符串。有时候,人们需要验证一个带有可选符号和可选小数点的浮点输入。
手动解析:
若要验证一个简单的“数字字符串”,可以使用glob:
# Bash / Ksh
if [[ -n $foo && $foo != *[!0123456789]* ]]; then
printf '"%s" is strictly numeric\n' "$foo"
else
printf '"%s" has a non-digit somewhere in it or is empty\n' "$foo"
fi >&2
但要避免使用[0-9]或[[:digit:]],因为在某些语言环境和某些系统中,它们可能匹配除01234567*9之8**外的字符。
在POSIX shell中,也可以使用case:
# POSIX
case $var in
'')
printf 'var is empty\n';;
*[!0123456789]*)
printf '%s has a non-digit somewhere in it\n' "$var";;
*)
printf '%s is strictly numeric\n' "$var";;
esac >&2
当然,如果只关心有效与无效,可以将它们结合起来:
# POSIX
case $var in
'' | *[!0123456789]*)
printf '%s\n' "$0: $var: invalid digit" >&2; exit 1;;
esac
如果需要允许前导负号,或者想要一个有效的浮点数或其他更复杂的内容,则有几种可能的方法。标准的glob无法表达这个,但可以去掉任何符号,然后进行比较:
# POSIX
case ${var#[-+]} in # notice ${var#prefix} substitution to trim sign
'')
printf 'var is empty\n';;
.)
printf 'var is just a dot\n';;
*.*.*)
printf '"%s" has more than one decimal point in it\n' "$var";;
*[!0123456789.]*)
printf '"%s" has a non-digit somewhere in it\n' "$var";;
*)
printf '"%s" looks like a valid float\n' "$var";;
esac >&2
在Bash中,我们可以使用扩展glob:
# Bash -- 在 4.1 之前的版本中,必须显式启用扩展 globs。
# 检查变量是否为全数字。
shopt -s extglob
[[ $var = +([0123456789]) ]]]
更复杂的情况:
# Bash / ksh
shopt -s extglob # 在 ksh 和 Bash 4.1 或更新版本中不需要
if [[ $foo = @(*[0123456789]*|!([+-]|)) && $foo = ?([+-])*([0123456789])?(.*([0123456789])) ]]; then
echo 'foo is a floating-point number'
fi
在支持扩展模式匹配的shell中,还可以使用case...esac。$foo的前导测试是为了确保它至少包含一个数字,不是空的,并且不仅由+或-本身组成。
如果对“有效数字”的定义更加复杂,或者需要在传统的Bourne shell中使用的解决方案,则可以使用外部工具的正则表达式语法。这里是一个可移植版本(在这里详细说明),使用awk(不是按行处理的egrep,所以不会被包含换行符的变量欺骗):
# Bourne
if awk -- 'BEGIN {exit !(ARGV[1] ~ /^[-+]?([0123456789]+\.?|[0123456789]*\.[0123456789]+)$/)}' "$foo"; then
printf '"%s" is a number\n' "$foo"
else
printf '"%s" is not a number\n' "$foo"
fi
Bash 3及以上版本在[[...]]结构中支持正则表达式。
# Bash
# 必须将 regexp 保存在变量中,并进行扩展,以便向后兼容 < 3.2 版本
regexp='^[-+]?[0123456789]*(\.[0123456789]*)?#39;
if [[ $foo = *[0123456789]* && $foo =~ $regexp ]]; then
printf '"%s" looks rather like a number\n' "$foo"
else
printf '"%s" doesn'\''t look particularly numeric to me.\n' "$foo"
fi
使用[ 和printf进行解析(或“使用eq”)
# 使用 ksh 时失败
if [ "$foo" -eq "$foo" ] 2>/dev/null; then
printf '"%s" is an integer\n' "$foo"
fi
[ 解析变量并将其解释为十进制整数,因为有了-eq。如果解析成功,则测试显然为true;如果失败,则[ 打印一个错误消息,2> / dev / null隐藏并设置一个与0不同的状态。但是,如果shell是ksh,则此方法会失败,因为ksh将变量评估为算术表达式(这将构成任意命令注入漏洞)。
请注意:以下使用printf的技巧(不受所有shell支持,而且支持的浮点表示列表也因shell而异;更不用说,可能存在跨平台问题)是错误的:
if printf %f "$foo" >/dev/null 2>&1; then
printf '"%s" is a float\n' "$foo"
fi
关于a、A、e、E、f、F、g或G格式修饰符的参数,POSIX规定,如果前导字符是单引号或双引号,则值应该是在后面的单引号或双引号后的字符的底层代码集中的数字值。因此,当foo扩展为具有前导单引号或双引号的字符串时,此方法会失败:上一个命令将高高兴兴地将字符串验证为浮点数。当foo扩展为具有前导0x的数字时,它也返回0,在shell脚本中是有效的数字,但在其他地方可能不起作用。
可以使用%d来解析整数。请注意,解析可能(应该?)是区域设置相关的。
了解更多shell实用技巧,快速掌握大厂一线经验
如果您觉得文章内容对你有一点帮助可以关注我,我在头条平台会持续分享更多实用的shell技巧和最佳实践,如果想系统的快速学习shell的各种高阶用法和生产环境避坑指南可以看看 《shell脚本编程最佳实践》专栏 ,专栏里有更多的实用小技巧和脚本代码分享。