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


596

我只是不知道如何确定传递给脚本的参数是否为数字。

我要做的就是这样:

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

有什么帮助吗?


17
顺便test && echo "foo" && exit 0 || echo "bar" && exit 1说一句- 您正在使用的方法可能会产生一些意想不到的副作用-如果回声失败(可能输出到封闭的FD),exit 0则会跳过,然后代码将尝试执行echo "bar"。如果它也失败了,那么&&条件将失败,甚至不会执行exit 1!使用实际if语句而不是&&/ ||不太可能导致意外的副作用。
查尔斯·达菲

@CharlesDuffy这是一种非常聪明的想法,大多数人仅在必须查找毛虫时才了解...!我从不认为回声会返回失败。
卡米洛·马丁

6
参加聚会有点晚,但是我知道查尔斯写过的危险,因为我也很早以前也必须经历这些危险。因此,这是一条100%防呆(且易于[[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }理解)的代码行: 大括号表示不应在子shell中执行操作(肯定会用()括号代替)。警告:切勿错过最后一个分号。否则,您可能会导致bash打印出最丑陋(也是最无意义的)错误消息……
语法

5
除非您不删除引号,否则它在Ubuntu中不起作用。因此,它应该只是[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
特维诺

4
您需要更具体地说明“数字”的含义。一个整数?定点数?科学(“ e”)表示法?是否存在要求的范围(例如64位无符号值),或者您允许任何可写的数字?
Toby Speight,

Answers:


801

一种方法是使用正则表达式,如下所示:

re='^[0-9]+$'
if ! [[ $yournumber =~ $re ]] ; then
   echo "error: Not a number" >&2; exit 1
fi

如果该值不一定是整数,请考虑适当修改正则表达式;例如:

^[0-9]+([.][0-9]+)?$

...或使用带符号处理数字:

^[+-]?[0-9]+([.][0-9]+)?$

7
此方法为+1,但要注意小数点,以“ 1.0”或“ 1,0”为例进行此测试,会打印“错误:不是数字”。
sourcerebels

15
我找到''exec>&2; 回声...'' 只是“回声...>&2”
2009年

5
@Ben您真的要处理多个减号吗?我会做它^-?,而不是^-*,除非你实际上做的工作要正确处理多个倒置。
Charles Duffy

4
@SandraSchlichting使所有将来的输出传递到stderr。这里并不是一个真正的问题,只有一个回声,但是在错误消息跨越多行的情况下,我倾向于养成这种习惯。
Charles Duffy 2012年

29
我不确定为什么必须将正则表达式保存在变量中,但是如果出于兼容性考虑,我认为没有必要。您可以直接直接应用表达式:[[ $yournumber =~ ^[0-9]+$ ]]
konsolebox

282

没有bashisms(即使在System V sh中也可以工作),

case $string in
    ''|*[!0-9]*) echo bad ;;
    *) echo good ;;
esac

这将拒绝空字符串和包含非数字的字符串,并接受其他所有内容。

负数或浮点数需要做一些额外的工作。一个想法是在第一个“不良”模式中排除-/ .并添加更多包含其不当使用的“不良”模式(?*-*/ *.*.*


20
+1-这是回到原始Bourne shell的惯用的,可移植的方式,并且内置了对glob样式通配符的支持。如果您来自其他编程语言,它看起来怪异,但它比各种报价问题和无尽的向后/侧身兼容性问题的脆性应对着更优雅if test ...
tripleee

6
您可以将第一行更改为${string#-}(在旧的Bourne外壳中不起作用,但在任何POSIX外壳中都有效)以接受负整数。
吉尔斯(Gilles)'“ SO-不要邪恶”

4
而且,这很容易扩展为浮点数-只需添加'.' | *.*.*到不允许的模式,然后在允许的字符中添加点即可。同样,您可以先允许一个可选的符号,尽管那样我宁愿case ${string#[-+]}直接忽略该符号。
Tripleee 2014年

请参阅以下内容以处理带符号整数:stackoverflow.com/a/18620446/2013911
Niklas Peter

2
@Dor不需要引号,因为case命令始终不对该单词执行单词拆分和路径名生成。(但是,如果要确定模式匹配字符是文字​​还是特殊字符,则可能需要引用大小写扩展形式。)
jilles

193

以下解决方案也可以在基本的Shell(例如Bourne)中使用,而无需使用正则表达式。基本上,任何使用非数字的数值评估操作都将导致错误,该错误在shell中将被隐式视为false:

"$var" -eq "$var"

如:

#!/bin/bash

var=a

if [ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null; then
  echo number
else
  echo not a number
fi

您还可以测试$?该操作的返回码更为明确:

[ -n "$var" ] && [ "$var" -eq "$var" ] 2>/dev/null
if [ $? -ne 0 ]; then
   echo $var is not number
fi

重定向标准错误是为了隐藏bash打印出来的“期望的整数表达式”消息,以防我们没有数字。

注意事项(由于以下评论):

  • 带小数点的数字被标识为有效的“数字”
  • 使用[[ ]]代替[ ]将始终评估为true
  • 大多数非Bash外壳程序始终会将此表达式评估为 true
  • Bash中的行为是未记录的,因此可能会更改而不会发出警告
  • 如果该值在数字后出现空格(例如“ 1 a”),则会产生错误,例如 bash: [[: 1 a: syntax error in expression (error token is "a")
  • 如果该值与var-name相同(例如i =“ i”),则产生错误,例如 bash: [[: i: expression recursion level exceeded (error token is "i")

8
我仍然建议这样做(但带引号的变量允许留空字符串),因为无论如何,结果都保证可以用作Bash中的数字。
l0b0

21
注意使用单括号;[[ a -eq a ]]计算为真(两个参数都转换为零)
Tgr 2012年

3
非常好!请注意,这仅适用于整数,不适用于任何数字。我需要检查单个参数,该参数必须为整数,因此效果很好:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
haridsv

6
我强烈建议不要使用此方法,因为数量很少的外壳程序,其[内置函数会将参数评估为算术值。在ksh93和mksh中都是如此。此外,由于这两个都支持数组,因此很容易进行代码注入。请改用模式匹配。
ormaaj 2014年

3
@AlbertoZaccagni,在当前的bash版本中,这些值仅使用数字上下文规则解释,[[ ]]而不用于[ ]。也就是说,这种行为在POSIX标准test和bash自己的文档中均未指定;未来的bash版本可以修改行为以匹配ksh而不会违反任何已记录的行为承诺,因此不能保证持久地依赖其当前行为是安全的。
查尔斯·达菲

43

这将测试一个数字是否为非负整数并且是否与外壳无关(即没有bashisms)并且仅使用外壳内置函数:

不正确

因为这个第一个答案(如下)允许整数中包含字符,只要该变量中的第一个不是第一个。

[ -z "${num##[0-9]*}" ] && echo "is a number" || echo "is not a number";

正确

正如jilles他的回答中所评论和建议的那样,这是使用shell模式进行操作的正确方法。

[ ! -z "${num##*[!0-9]*}" ] && echo "is a number" || echo "is not a number";

5
这不能正常工作,它接受以数字开头的任何字符串。请注意,$ {VAR ## WORD}中的WORD和相似的是shell模式,而不是正则表达式。
jilles

2
你能把那个表达翻译成英文吗?我真的很想使用它,但是即使仔细阅读了bash手册页,我还是不太理解它。
CivFan

2
*[!0-9]*是一种模式,可匹配所有至少包含1个非数字字符的字符串。${num##*[!0-9]*}是“参数扩展”,我们在其中获取num变量的内容并删除与模式匹配的最长字符串。如果参数扩展的结果不为空(! [ -z ${...} ]),则为数字,因为它不包含任何非数字字符。
mrucci

不幸的是,如果参数中有任何数字,即使它不是有效数字,此操作也会失败。例如“ exam1ple”或“ a2b”。
studgeek

都失败了122s :-(
Hastur

40

没有人建议bash 扩展模式匹配

[[ $1 == ?(-)+([0-9]) ]] && echo "$1 is an integer"

5
格伦,我shopt -s extglob从您的帖子中删除了(我赞成,这是我最喜欢的答案之一),因为在条件构造中您可以阅读:使用==!=运算符时,运算符右侧的字符串被视为模式并与之匹配根据下面的“ 模式匹配”中描述的规则,就好像extglob启用了shell选项一样。希望您不要介意!
gniourf_gniourf 2015年

在这种情况下,您不需要shopt extglob...这是一件好事!
gniourf_gniourf 2015年

适用于简单的整数。

2
@Jdamian:是的,它是在Bash 4.1中添加的(2009年底发布了…Bash 3.2在2006年发布了……现在是一个古老的软件,对那些过去受困的人感到抱歉)。此外,您可能会争辩到extglobs是在2.02版(1998年发布)中引入的,并且在<2.02版中不起作用…现在,您的评论将作为对较旧版本的警告。
gniourf_gniourf

1
其中的变量[[...]]不受字分割或全局扩展的约束。
格伦

26

我对直接在shell中解析数​​字格式的解决方案感到惊讶。shell不适用于此,它是用于控制文件和进程的DSL。下方有一些足够多的解析器,例如:

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
}

将“%f”更改为所需的任何特定格式。


4
isnumber(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; }
Gilles Quenot 2012年

4
@sputnick您的版本破坏了原始函数固有的(且有用的)返回值语义。因此,相反,只需按原样保留函数并使用它:isnumber 23 && echo "this is a number" || echo "not a number"
michael

4
这还不应该2>/dev/null,这样isnumber "foo"就不会污染stderr吗?
gioele 2014年

4
将bash之类的现代shell称为“用于控制文件和进程的DSL”,却忽略了它们的用途已不止这些-一些发行版已在其上构建了整个程序包管理器和Web界面(可能如此丑陋)。批处理文件符合您的描述,因为即使设置变量也很困难。
卡米洛·马丁

7
有趣的是,您试图通过复制其他语言的习惯用法来变得聪明。不幸的是,这在shell中不起作用。Shell非常特殊,如果您对Shell没有足够的了解,您可能会编写残破的代码。您的代码已损坏:isnumber "'a"将返回true。这在POSIX规范中有记录,您将在其中阅读:如果前导字符是单引号或双引号,则该值应是单引号或双引号后面的字符的基础代码集中的数值。 。
gniourf_gniourf 2015年

16

我看着答案,发现没有人想到浮点数(带点)!

使用grep也很棒。
-E表示扩展的正则表达式
-q表示安静(不回显)
-qE是两者的组合。

要在命令行中直接测试:

$ 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

在bash脚本中使用:

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

要匹配仅整数,请使用以下命令:

# change check line to:
check=`echo "$1" | grep -E ^\-?[0-9]+$`

使用由Triple_r和Tripleee使用awk的解决方案可与浮点数一起使用。
肯·杰克逊

感谢您,非常感谢!因为问题实际上是如何检查它是否是一个数字,而不仅仅是整数。
Tanasis

我也谢谢你塔纳西斯!让我们总是互相帮助。
Sergio Abreu

12

只是对@mary的跟进。但是因为我没有足够的代表,所以无法将其发布为该帖子的评论。无论如何,这是我所使用的:

isnum() { awk -v a="$1" 'BEGIN {print (a == a + 0)}'; }

如果参数为数字,则该函数将返回“ 1”,否则将返回“ 0”。这适用于整数和浮点数。用法类似于:

n=-2.05e+07
res=`isnum "$n"`
if [ "$res" == "1" ]; then
     echo "$n is a number"
else
     echo "$n is not a number"
fi

4
打印数字没有设置退出代码有用。'BEGIN { exit(1-(a==a+0)) }'是稍硬神交,但可以在其返回true或false就像一个函数中使用[grep -q等等
tripleee

9
test -z "${i//[0-9]}" && echo digits || echo no no no

${i//[0-9]}$i空值中的任何数字替换为空字符串,请参见man -P 'less +/parameter\/' bash-z检查结果字符串的长度是否为零。

如果您还想排除$i为空的情况,则可以使用以下一种构造:

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部分。每天学习新东西。:)
David

@sjas您可以轻松地添加\-正则表达式来解决该问题。使用[0-9\-\.\+]到账户浮标和有符号数。
user2683246

@sjas ok,我的错
user2683246

@sjasecho $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' && echo yes || echo no
user2683246 '19

9

对于我的问题,我只需要确保用户不会意外输入一些文本,因此我尝试使其保持简单易读

isNumber() {
    (( $1 )) 2>/dev/null
}

根据手册页,这几乎可以满足我的要求

如果表达式的值不为零,则返回状态为0

为了防止“可能是数字”的字符串出现讨厌的错误消息,我忽略了错误输出

$ (( 2s ))
bash: ((: 2s: value too great for base (error token is "2s")

这更像isInteger。但是,谢谢!
Naheel

8

老问题了,但我只是想提出自己的解决方案。这不需要任何奇怪的shell技巧,也不需要依靠永远不会出现的东西。

if [ -n "$(printf '%s\n' "$var" | sed 's/[0-9]//g')" ]; then
    echo 'is not numeric'
else
    echo 'is numeric'
fi

基本上,它只是从输入中删除所有数字,如果您留下的字符串长度不为零,则它不是数字。


对于空失败var
gniourf_gniourf 2015年

或尾随换行符之类的变量$'0\n\n\n1\n\n\n2\n\n\n3\n'
gniourf_gniourf 2015年

7

无法发表评论,因此我将添加自己的答案,这是使用bash模式匹配对glenn jackman的答案的扩展。

我最初的需求是识别数字并区分整数和浮点数。函数定义推导为:

function isInteger() {
    [[ ${1} == ?(-)+([0-9]) ]]
}

function isFloat() {
    [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}

我使用单元测试(通过shUnit2)来验证我的模式是否按预期工作:

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

}

注意:可以修改isFloat模式,使其对小数点(@(.,))和E符号(@(Ee))的容忍度更高。我的单元测试仅测试整数或浮点值,而不测试任何无效输入。


抱歉,不了解如何使用编辑功能。
3ronco

6

我会尝试这样的:

printf "%g" "$var" &> /dev/null
if [[ $? == 0 ]] ; then
    echo "$var is a number."
else
    echo "$var is not a number."
fi

注意:这会将nan和inf识别为数字。


2
重复或也许更适合作为pixelbeat答案的注释(使用%f反正可能更好)
michael

3
而不是检查以前的状态代码,为什么不将其if本身放进去呢?那是什么if...if printf "%g" "$var" &> /dev/null; then ...
Camilo Martin 2014年

3
这还有其他警告。它将验证空字符串以及类似的字符串'a
gniourf_gniourf 2015年

6

@charles Dufy和其他人已经给出了明确的答案。一个纯bash解决方案将使用以下方法:

string="-12,345"
if [[ "$string" =~ ^-?[0-9]+[.,]?[0-9]*$ ]]
then
    echo $string is a number
else
    echo $string is not a number
fi

尽管对于实数,在小数点前没有数字是强制性的。

为了提供对浮点数和科学计数法的更彻底的支持(C / Fortran中的许多程序,否则将以这种方式导出浮点数),此行的有用补充如下:

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

因此,如果您要查找任何特定类型,则可以找到一种区分数字类型的方法:

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

注意:我们可以列出十进制和科学计数法的句法要求,其中之一是允许逗号作为小数点以及“。”。然后,我们断言必须只有一个这样的小数点。[Ee]浮点数中可以有两个+/-符号。我从Aulu的工作中学到了一些规则,并针对诸如''-''-E-1''0-0'之类的错误字符串进行了测试。这是我似乎正在使用的regex / substring / expr工具:

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

6
[[ $1 =~ ^-?[0-9]+$ ]] && echo "number"

不要忘记-添加负数!


bash的最低版本是什么?我刚得到bash:条件二进制运算符预期的bash:意外标记'=〜'附近的语法错误
Paul Hargreaves

1
@PaulHargreaves =~至少可以追溯到bash 3.0。
吉尔斯(Gilles)'“ SO-别再邪恶了”

@PaulHargreaves您的第一个操作数可能有问题,例如,引号太多或类似的内容
Joshua Clayton

我问了@JoshuaClayton版本,因为它非常 非常在Solaris 7中,我们仍然有旧bash和它不支持=〜
保罗·哈格里夫斯

6

这可以通过使用grep查看所讨论的变量是否匹配扩展的正则表达式来实现。

测试整数1120

yournumber=1120
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

输出: Valid number.

测试非整数1120a

yournumber=1120a
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
    echo "Valid number."
else
    echo "Error: not a number."
fi

输出: Error: not a number.


说明

  • grep,该-E开关允许我们使用扩展正则表达式'^[0-9]+$'。此正则表达式表示变量从变量的开始到结束应仅[]包含数字0-90到9,并且至少应包含一个字符。^$+
  • grep,在-q安静的开关关闭任何输出是否发现任何东西。
  • if 检查的退出状态 grep。退出状态0表示成功,更大的状态表示错误。该grep命令的退出状态为0是否找到匹配项以及1何时没有找到匹配项;

因此,将所有内容放在一起,在if测试中,我们echo将变量$yournumber并通过|管道将grep其通过管道-q-E扩展的正则表达式'^[0-9]+$'表达式进行静默匹配。的退出状态grep将是0,如果grep成功找到了一根火柴,1如果它没有。如果匹配成功,我们echo "Valid number."。如果匹配失败,我们echo "Error: not a number."


对于浮球或双打

我们可以将正则表达式从'^[0-9]+$'更改'^[0-9]*\.?[0-9]+$'为float或double。

测试浮子 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

输出: 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

输出: Error: not a number.


对于负数

要允许负整数,只需将正则表达式从更改'^[0-9]+$''^\-?[0-9]+$'

要允许负浮点数或双精度数,只需将正则表达式从更改为'^[0-9]*\.?[0-9]+$'即可'^\-?[0-9]*\.?[0-9]+$'


你说得对,@ CharlesDuffy。谢谢。我整理了答案。
约瑟夫(Shih)

对,你又回来了,@ CharlesDuffy。固定!
Joseph Shih

1
LGTM;编辑后的答案是我的+1。在这一点上,我唯一要做的事情就是观点问题,而不是正确性问题(f / e,使用[-]代替\-[.]代替代替\.会更加冗长,但这意味着您的字符串无需更改即可)在反斜杠被占用的上下文中重新使用)。
查尔斯·达菲

4

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_03.html

您还可以使用bash的字符类。

if [[ $VAR = *[[:digit:]]* ]]; then
 echo "$VAR is numeric"
else
 echo "$VAR is not numeric"
fi

数字将包括空格,小数点和浮点数的“ e”或“ E”。

但是,如果指定C样式的十六进制数字,即“ 0xffff”或“ 0XFFFF”,则[[:digit:]]返回true。bash有点陷阱,bash允许您执行“ 0xAZ00”之类的操作,并且仍将其算作一个数字(这不是GCC编译器的一些怪癖,它使您可以将0x表示法用于除16以外的基数吗? )

如果您的输入完全不受信任,则可能需要先测试“ 0x”或“ 0X”是否为数字,除非您想接受十六进制数字。这将通过以下方式实现:

if [[ ${VARIABLE:1:2} = "0x" ]] || [[ ${VARIABLE:1:2} = "0X" ]]; then echo "$VAR is not numeric"; fi

17
[[ $VAR = *[[:digit:]]* ]]如果变量包含数字,则返回true ,如果它整数,则返回true 。
glenn jackman 2012年

[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"版画numeric。在bash版本中进行了测试3.2.25(1)-release
詹达米安

1
@ultraswadable,您的解决方案将检测到那些字符串,这些字符串至少包含一个(或没有)任何其他字符包围的数字。我投票了。
詹达米安

因此,显而易见的正确方法是扭转这种状况,并使用[[ -n $VAR && $VAR != *[^[:digit:]]* ]]
eschwartz

@eschwartz,您的解决方案不适用于负数
天使

4

最简单的方法是检查它是否包含非数字字符。您将所有数字字符替换为空并检查其长度。如果有长度,则不是数字。

if [[ ! -n ${input//[0-9]/} ]]; then
    echo "Input Is A Number"
fi

2
处理负数将需要更复杂的方法。
安迪

...或可选的正号。
三人房

@tripleee我想看看您的方法,如果您知道该怎么做。
安迪

4

因为我最近不得不对此进行修改,并且最喜欢单元测试的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任何操作。
汤姆·费内奇

不错,的确使returns令人困惑,并使它的可读性降低。是否使用function关键字更多地是个人风格的问题,至少我从一个衬板中删除了它们以节省一些空间。谢谢。
3ronco

不要忘了单行版本的测试后需要分号。
汤姆·费内奇

2
isNumber将在其中包含数字的任何字符串上返回“ true”。
DrStrangepork

@DrStrangepork确实,我的false_values数组缺少这种情况。我会调查一下。感谢您的提示。
3ronco

4

我使用expr。如果尝试将零添加到非数字值,它将返回非零值:

if expr -- "$number" + 0 > /dev/null 2>&1
then
    echo "$number is a number"
else
    echo "$number isn't a number"
fi

如果您需要非整数,则可能可以使用bc,但是我不认为它bc具有完全相同的行为。将零加到非数字上将得到零,并且它也返回零值。也许你可以结合bcexpr。用于bc向加上零$number。如果答案为0,则尝试expr验证其$number不为零。


1
这很糟糕。为了使它更好,你应该使用expr -- "$number" + 0; 但这仍然会假装0 isn't a number。来自man exprExit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
gniourf_gniourf,2015年

3

我喜欢Alberto Zaccagni的回答。

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

重要的先决条件:-不产生子外壳-不调用RE解析器-大多数外壳应用程序不使用实数

但是,如果$var是复杂的(例如,关联数组访问),并且该数字是一个非负整数(大多数用例),那么这也许更有效?

if [ "$var" -ge 0 ] 2> /dev/null; then ..

2

如果您提供格式字符串“%f”或“%i”,我将使用printf作为其他答案,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}" "Error: Found $var - An integer is required."))) && { echo "$var+5" | bc; }


2

正则表达式 vs 参数扩展

作为Charles Duffy的答案作品,但仅用于正则表达式,我知道这很慢,我想展示另一种方式,仅使用参数扩展

仅对于正整数:

is_num() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}

对于整数:

is_num() { 
    local chk=${1#[+-]};
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

然后进行浮动:

is_num() { 
    local chk=${1#[+-]};
    chk=${chk/.}
    [ "$chk" ] && [ -z "${chk//[0-9]}" ]
}

要匹配初始请求:

set -- "foo bar"
is_num "$1" && VAR=$1 || echo "need a number"
need a number

set -- "+3.141592"
is_num "$1" && VAR=$1 || echo "need a number"

echo $VAR
+3.141592

现在比较一下:

根据Charles Duffy的回答,有相同的检查:

cdIs_num() { 
    local re='^[+-]?[0-9]+([.][0-9]+)?$';
    [[ $1 =~ $re ]]
}

一些测试:

if is_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num foo;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 25;then echo It\'s a number ;else echo Not a number;fi
It's a number
if is_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if cdIs_num 3+4;then echo It\'s a number ;else echo Not a number;fi
Not a number
if is_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number
if cdIs_num 3.1415;then echo It\'s a number ;else echo Not a number;fi
It's a number

没关系。现在这需要多少时间(在我的树莓派上):

time for i in {1..1000};do is_num +3.14159265;done
real    0m2.476s
user    0m1.235s
sys     0m0.000s

然后使用正则表达式:

time for i in {1..1000};do cdIs_num +3.14159265;done
real    0m4.363s
user    0m2.168s
sys     0m0.000s

我同意,无论如何,当我可以使用参数扩展时,我宁愿不使用正则表达式...滥用RE将使bash脚本变慢
F. Hauri

答案已编辑:不需要extglob(更快一点)!
F.豪里

@CharlesDuffy,在我的树莓派上,使用我的版本进行1000次迭代将花费2.5秒,而使用你的版本将进行4.4秒!
F. Hauri

不能与之争论。👍
查尔斯·达菲

1

捕捉负数:

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

此外,这要求首先启用扩展的glob。这是仅Bash功能,默认情况下已禁用。
三胞胎

使用==或!= 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
Badr Elmers,

@BadrElmers感谢您的更新。这似乎是一种新行为,在我的Bash 3.2.57(MacOS Mojave)中是不正确的。我看到它按您在4.4中描述的那样工作。
三点

1

您也可以这样使用“ 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
这是非常危险的。不要在外壳中评估未经验证的算法。必须首先以其他方式对其进行验证。
ormaaj 2014年

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

-\?如果您不接受负整数,则删除in grep匹配模式。


2
因缺乏解释而投降。这是如何运作的?它看起来复杂而脆弱,并且不清楚它将接受什么输入。(例如,至关重要的是删除空格吗?为什么?它会说一个嵌入空格的数字是有效数字,可能不希望如此。)
Tripleee

1

在这里用正则表达式做同样的事情,该正则表达式测试整个部分和小数部分,并用点分隔。

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

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

您能解释一下为什么您的答案与其他旧答案(例如,查尔斯·达菲(Charles Duffy)的答案)根本不同吗?好吧,您的答案实际上是.
无效的,

不确定在这里了解单个周期...这是一个或零个周期...。但是没什么根本不同的地方,只是发现正则表达式更易于阅读。
杰罗姆'18年

也使用*应该匹配更多实际案例
Jerome

问题是您要匹配空字符串a=''和仅包含句点的字符串,a='.'因此您的代码有点破...
gniourf_gniourf

0

我使用以下(用于整数):

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

几乎是正确的(您正在接受空字符串),但是却很复杂,难以理解。
吉尔(Gilles)“所以,别再邪恶了”

2
错误的:您正在接受-n,等等(由于echo),并且您正在接受带有尾随换行符的变量(由于$(...))。顺便说一句,print这不是有效的shell命令。
gniourf_gniourf 2015年

0

我尝试了Ultrasawblade的食谱,因为它对我来说似乎是最实用的方法,但无法使其起作用。最后,我设计了另一种方法,与其他参数替换方法一样,这次使用正则表达式替换:

[[ "${var//*([[:digit:]])}" ]]; && echo "$var is not numeric" || echo "$var is numeric"

它删除$ var中的每个:digit:类字符,并检查是否留有空字符串,这意味着原始字符只是数字。

我喜欢这个的原因是它的占地面积小和灵活性。以这种形式,它仅适用于以10为基数的非定界整数,尽管您可以确定地使用模式匹配使其适合其他需求。


阅读mrucci的解决方案,它看起来与我的解决方案几乎相同,但是使用常规的字符串替换而不是“ sed样式”。两者都使用相同的规则进行模式匹配,并且都是AFAIK可互换的解决方案。
ata

sed是POSIX,而您的解决方案是bash。两者都有用途
v010dya

0

快速与肮脏:我知道这不是最优雅的方法,但是我通常只是在其中添加一个零并测试结果。像这样:

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))将返回0或炸弹,如果$ 1不是整数。例如:

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
}

注意:我想我从没想过要检查会导致整个脚本炸弹爆炸的浮点数或混合类型……就我而言,我不希望它进一步发展。我将使用mrucci的解决方案和Duffy的正则表达式-它们似乎是bash框架中最强大的...


4
这接受像的算术表达式1+1,但是拒绝一些以0s 开头的正整数(因为这08是无效的八进制常量)。
吉尔斯(Gillles)“所以-别再邪恶了”

2
这也有其他问题:0不是数字,并且可以任意注入代码,请尝试:isInteger 'a[$(ls)]'。哎呀
gniourf_gniourf 2015年

1
并且的扩展名$((...))是无引号的,数值IFS=123会更改它。
以撒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.