在Bash中如何测试一个变量是否为数字?

866

我无法弄清楚如何确保传递给我的脚本的参数是数字还是其他类型。

我想要做的只是像这样:

test *isnumber* $1 && VAR=$1 || echo "need a number"

需要帮助吗?


21
顺便提一下 - 你正在使用的 test && echo "foo" && exit 0 || echo "bar" && exit 1 方法可能会产生一些意想不到的副作用 - 如果 Echo 失败了(也许输出到了一个关闭的FD),exit 0 将被跳过,然后代码将尝试 echo "bar"。如果这也失败了,&& 条件将失败,甚至不会执行 exit 1! 使用实际的 if 语句而不是 &&/|| 更不容易出现意外的副作用。 - Charles Duffy
12
有点晚参加派对了,但我知道Charles所写的危险性,因为我之前也经历过。这里是一个100%无懈可击(而且易于阅读)的命令行:[[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; } 大括号表示不应该在子shell中执行事情(如果使用圆括号 () 就会明确这种情况)。注意:永远不要漏掉最后的分号。否则,你可能会导致bash打印出最丑陋(并且毫无意义)的错误消息... - syntaxerror
8
除非您不删除引号,否则它在Ubuntu中无法正常工作。因此,应该只是 [[ 12345 =~ ^ [0-9] + $]] && echo OKKK || echo NOOO - Treviño
7
请提供更具体的信息,说明您所说的“number”是指什么。指整数?定点数?科学(“e”)表示法?是否需要在特定范围内(例如64位无符号值),或者允许任何可写成数字形式的数字? - Toby Speight
2
Bash确实提供了一种可靠的方法来确定一个数字是否为整数。{ VAR="asdfas" ; (( VAR )) ; echo $?; } 如果答案是'0',则该方程将正确失败,因为'0'不是整数。我几分钟前也遇到了同样的问题,并通过快速搜索找到了这个线程。我希望这能帮助其他人。其他人也接近了。 - That one guy from the movie
显示剩余5条评论
41个回答

5

由于最近需要修改此代码,并且喜欢karttu的单元测试方法,因此我对代码进行了修改并添加了其他解决方案,您可以自行尝试以查看结果:

#!/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

所以,isNumber() 包括破折号、逗号和科学计数法,因此在整数和浮点数上返回 TRUE。另一方面,isFloat() 在整数值上返回 FALSE,而 isInteger() 同样在浮点数上返回 FALSE。为了方便起见,这里提供一个单行表达式:

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 关键字,因为它没有任何有用的作用。此外,我不确定返回值的有用性。除非另有规定,否则函数将返回最后一个命令的退出状态,因此您不需要自己进行 return - Tom Fenech
不错,的确return语句会让代码变得混乱难读。使用function关键字与否更多是个人口味问题,至少我从一行代码中移除了它们以节省空间。谢谢。 - 3ronco
不要忘记在单行版本的测试后加上分号。 - Tom Fenech
3
isNumber会在任何包含数字的字符串中返回'true'。 - DrStrangepork
@DrStrangepork 的确,我的 false_values 数组缺少那个情况。我会去看看的。谢谢你的提示。 - 3ronco
尝试创建一个包含所有三种类型的组合正则表达式。虽然这是可能的,但会导致一个非常复杂的正则表达式乱码。使用互斥条件看起来更加简单易读。 - 3ronco

4

我使用printf,正如其他答案所提到的一样,如果你提供格式字符串"%f"或"%i",printf会为你进行检查。这比重新发明检查更容易,语法简单且短小,printf是无处不在的。因此,在我看来,这是一个不错的选择 - 你也可以使用以下思路来检查一系列事物,它不仅适用于检查数字。

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}" "错误:发现 $var - 需要输入整数。"))) && { echo "$var+5" | bc; }


注:此段代码为shell脚本,其中$var为变量名,可根据实际情况进行更改。

4

我喜欢Alberto Zaccagni的答案。

if [ "$var" -eq "$var" ] 2>/dev/null; then

重要先决条件: - 不产生子shell - 不调用RE解析器 - 大多数Shell应用不使用实数
但是,如果$var是复杂的(例如,关联数组访问),并且数字将是非负整数(大多数用例),那么这可能更有效?
if [ "$var" -ge 0 ] 2> /dev/null; then ..

这不仅对于复数(具有虚部)失败,而且对于浮点数(具有非整数部分)也是如此。 - Charles Duffy

2
几乎符合语法要求。只需要一个名为 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"

测试:

$ ./isnumtest 10
VAR is 10
$ ./isnumtest abc10
need a number
VAR is 

2
你也可以像这样使用 "let":

你也可以像这样使用 "let":

[ ~]$ 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
[ ~]$ 

但我更喜欢使用“=~”Bash 3+运算符,就像这个主题中的一些答案。


5
这非常危险。不要在 Shell 中计算未经验证的算术表达式,必须先以其他方式进行验证。 - ormaaj
@ormaaj 为什么这很危险?是指恶意数字或溢出吗?如果输入是您自己的值,那么它是否危险? - balupton

2

捕捉负数:

if [[ $1 == ?(-)+([0-9.]) ]]
    then
    echo number
else
    echo not a number
fi

此外,这需要先启用扩展的 globbing。这是一个仅在 Bash 中可用的功能,默认情况下被禁用。 - tripleee
@tripleee 扩展的 globbing 在使用 == 或 != 时会自动激活。当使用 '==' 和 '!=' 运算符时,运算符右侧的字符串被视为模式,并根据下面在“模式匹配”中描述的规则进行匹配,就好像启用了 extglob shell 选项一样。https://www.gnu.org/software/bash/manual/bashref.html#index-_005b_005b - Badr Elmers
@BadrElmers 感谢您的更新。这似乎是一种新的行为,而在我的Bash 3.2.57(MacOS Mojave)中并不正确。我看到它在4.4中像你描述的那样工作。 - tripleee

1

在这里我使用了一个正则表达式,用于测试整数部分和小数部分,它们用一个点号分隔。

re="^[0-9]*[.]{0,1}[0-9]*$"

if [[ $1 =~ $re ]] 
then
   echo "is numeric"
else
  echo "Naahh, not numeric"
fi

你能解释一下为什么你的答案与其他旧答案(例如Charles Duffy的答案)在根本上有所不同吗?嗯,你的答案实际上是有问题的,因为它验证了一个单独的句点“.”。 - gniourf_gniourf
不确定这里的单个句点是否理解正确...应该是期望一个或零个句点...但目前没有根本性的区别,只是发现正则表达式更易于阅读。 - Jerome
并且使用 * 应该匹配更多实际情况。 - Jerome
1
问题在于你正在匹配空字符串 a='' 和仅包含句点的字符串 a='.',因此你的代码有些错误... - gniourf_gniourf

1
printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."

如果您不接受负整数,请在grep匹配模式中删除-\?


3
因为缺少解释而被点踩。这是如何工作的?看起来很复杂且易碎,不清楚它到底接受哪些输入。例如,去除空格是否非常必要?为什么?它将会认为带有嵌入空格的数字是有效的数字,但这可能并不理想。 - tripleee

1
易于理解和兼容的解决方案,使用test命令:
test $myVariable -eq 0 2>/dev/null
if [ $? -le 1 ]; then echo 'ok'; else echo 'KO'; fi

如果myVariable = 0,则返回代码为0
如果myVariable > 0,则返回代码为1
如果myVariable不是整数,则返回代码为2。

0

我找到了一个相当简短的版本:

function isnum()
{
    return `echo "$1" | awk -F"\n" '{print ($0 != $0+0)}'`
}

这个代码如果字符串不是数字会返回0,那么如果你的字符串是“0”它就不能工作了吗? - naught101

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接