【问题标题】:How do I test if a variable is a number in Bash?如何测试变量是否是 Bash 中的数字?
【发布时间】:2010-10-22 20:33:48
【问题描述】:

我只是不知道如何确保传递给我的脚本的参数是一个数字。

我想做的就是这样:

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

有什么帮助吗?

【问题讨论】:

  • 顺便说一句——您使用的test && echo "foo" && exit 0 || echo "bar" && exit 1 方法可能会产生一些意想不到的副作用——如果回显失败(可能输出到关闭的FD),exit 0 将是跳过,然后代码将尝试echo "bar"。如果它也失败了,&& 条件将失败,它甚至不会执行exit 1!使用实际的 if 语句而不是 &&/|| 不太容易产生意外的副作用。
  • @CharlesDuffy 大多数人只有在必须追踪毛茸茸的虫子时才会想到这种非常聪明的想法......!我从没想过 echo 会返回失败。
  • 参加聚会有点晚了,但我知道查尔斯写的危险,因为我在很久以前也必须经历它们。所以这里有一个 100% 万无一失(并且可读性强)的行:[[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; } 花括号表示不应该在子shell 中执行事情(这肯定会使用() 括号代替)。警告:永远不要错过最后的分号。否则你可能会导致bash 打印出最丑陋(也是最无意义)的错误消息......
  • 它在 Ubuntu 中不起作用,除非您不删除引号。所以应该只是[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
  • 您需要更具体了解 "number" 的含义。一个整数?定点数?科学(“e”)符号?是否有要求的范围(例如 64 位无符号值),或者您是否允许任何可以写入的数字?

标签: linux bash shell


【解决方案1】:

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

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

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

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

...或者,处理带符号的数字:

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

【讨论】:

  • +1 对于这种方法,但要注意小数,例如使用“1.0”或“1,0”进行此测试会打印“错误:不是数字”。
  • @Ben 你真的要处理多个减号吗?除非您实际上正在做正确处理多个反转的工作,否则我会使用 ^-? 而不是 ^-*
  • @SandraSchlichting 使所有未来的输出到标准错误。这里没有真正的意义,只有一个回声,但这是我倾向于在错误消息跨越多行的情况下养成的习惯。
  • 我不确定为什么必须将正则表达式保存在变量中,但如果是为了兼容性我认为没有必要。您可以直接应用表达式:[[ $yournumber =~ ^[0-9]+$ ]].
  • @konsolebox 是的,兼容性。 =~ 右侧文字正则表达式中的反斜杠处理在 3.1 和 3.2 之间发生了变化,而赋值中的反斜杠处理在所有相关的 bash 版本中都是不变的。因此,遵循在使用=~ 匹配变量之前始终将正则表达式分配给变量的做法可以避免意外。我这样做是为了教导良好的习惯,即使这个特殊的正则表达式没有反斜杠转义。
【解决方案2】:

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

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

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

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

【讨论】:

  • +1 -- 这是回到原始 Bourne shell 的惯用、可移植方式,并且内置了对 glob 样式通配符的支持。如果你来自另一种编程语言,它看起来很诡异,但它比处理各种引用问题的脆弱性和if test ... 无休止的向后/横向兼容性问题要优雅得多
  • 您可以将第一行更改为 ${string#-}(这在古董 Bourne shell 中不起作用,但在任何 POSIX shell 中都适用)以接受负整数。
  • 另外,这很容易扩展到浮点数 - 只需将 '.' | *.*.* 添加到不允许的模式,并将点添加到允许的字符。同样,您可以在之前允许一个可选符号,但我更希望case ${string#[-+]} 简单地忽略该符号。
  • @Dor 不需要引号,因为 case 命令无论如何都不会对该单词执行分词和路径名生成。 (但是,case 模式的扩展可能需要引用,因为它决定了模式匹配字符是字面量还是特殊字符。)
  • 这似乎是最快的方法!看看my comparison
【解决方案3】:

以下解决方案也可用于基本的 shell,例如 Bourne,而不需要正则表达式。基本上任何使用非数字的数值计算操作都会导致错误,在 shell 中会被隐式认为是错误的:

"$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 打印出来的“预期整数表达式”消息,以防我们没有数字。

CAVEATS(感谢下面的 cmets):

  • 带小数点的数字被识别为有效的“数字”
  • 使用[[ ]] 而不是[ ] 将始终计算为true
  • 大多数非 Bash shell 将始终将此表达式计算为 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")

【讨论】:

  • 我仍然推荐这个(但引用的变量允许空字符串),因为无论结果如何都保证在 Bash 中作为数字可用什么。
  • 注意使用单括号; [[ a -eq a ]] 计算结果为真(两个参数都转换为零)
  • 非常好!请注意,这仅适用于整数,而不适用于任何数字。我需要检查一个必须是整数的参数,所以效果很好:if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
  • 我强烈建议不要使用这种方法,因为 [ 内置函数会将参数评估为算术的 shell 数量不少。在 ksh93 和 mksh 中都是如此。此外,由于这两个都支持数组,因此很容易进行代码注入。请改用模式匹配。
  • @AlbertoZaccagni,在当前版本的 bash 中,这些值仅使用数字上下文规则解释 [[ ]] 而不是 [ ]。也就是说,test 的 POSIX 标准和 bash 自己的文档都未指定此行为;未来版本的 bash 可以修改行为以匹配 ksh 而不会违反任何记录在案的行为承诺,因此不能保证持续依赖其当前行为是安全的。
【解决方案4】:

没有人建议 bash 的 extended pattern matching:

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

或使用new POSIX style:

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

【讨论】:

  • 格伦,我从你的帖子中删除了shopt -s extglob(我赞成,这是我最喜欢的答案之一),因为在Conditional Constructs 中你可以阅读:== 和使用!= 运算符,运算符右侧的字符串被视为模式并根据Pattern Matching 中描述的规则进行匹配,就像启用了extglob shell 选项一样。 我希望你别介意!
  • 在这种情况下,你不需要shopt extglob...这是一件好事!
  • @Jdamian:你说得对,这是在 Bash 4.1 中添加的(2009 年底发布……Bash 3.2 发布于 2006 年……它现在是一个古董软件,对不起那些停留在过去)。此外,您可能会争辩说 extglobs 是在 2.02 版(1998 年发布)中引入的,并且在
  • [[...]] 中的变量不受分词或全局扩展的影响。
  • @ThiagoConrado,在手册中查找 [[...]](或在 bash 提示符下查找 help [[):只有 == 的右侧是一个模式。
【解决方案5】:

这会测试一个数字是否为非负整数。它独立于 shell(即没有 bashisms)并且仅使用 shell 内置:

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

建议的此答案的先前版本:

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

但这是INCORRECT,因为它接受任何以数字开头的字符串,如jilles suggested

【讨论】:

  • 这不能正常工作,它接受任何以数字开头的字符串。请注意 ${VAR##WORD} 和类似中的 WORD 是 shell 模式,而不是正则表达式。
  • 你能把那个表达翻译成英文吗?我真的很想使用它,但我对它的理解不足以信任它,即使在仔细阅读了 bash 手册页之后也是如此。
  • *[!0-9]* 是一种匹配所有至少有 1 个非数字字符的字符串的模式。 ${num##*[!0-9]*} 是一个“参数扩展”,我们在其中获取 num 变量的内容并删除与模式匹配的最长字符串。如果参数扩展的结果不为空(! [ -z ${...} ])那么它是一个数字,因为它不包含任何非数字字符。
  • 不幸的是,如果参数中有任何数字,即使它不是有效数字,也会失败。例如“exam1ple”或“a2b”。
  • 但这很好,因为“exam1ple”、“a2b”和“122s”都不是数字。
【解决方案6】:

(第 2 次)完全重写此答案:2021 年 6 月 27 日。

一些性能和兼容性提示

对于不同种类的测试,有一些非常不同的方法。

我查看了最相关的方法并建立了这个比较。

无符号整数is_uint()

这些函数实现代码来评估表达式是否为无符号整数,即完全由数字组成。

  • 使用参数扩展

    (这是我之前的做法!)

    isuint_Parm() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}
    
  • 使用 fork 到 grep

    isuint_Grep() { grep -qE '^[0-9]+$' <<<"$1"; }
    

    我只测试了一次这个方法,因为它很慢。这只是为了说明不该做什么。

  • 使用 整数功能

    isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
    
  • 使用 case

    isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
    
  • 使用的正则表达式

    isuint_Regx() { [[ $1 =~ ^[0-9]+$ ]] ;}
    

有符号整数is_int()

这些函数实现代码来评估一个表达式是否是一个有符号整数,即如上所述,但允许在数字前有一个可选的符号。

  • 使用参数扩展

    isint_Parm() { local chk=${1#[+-]}; [ "$chk" ] && [ -z "${chk//[0-9]}" ] ;}
    
  • 使用 整数功能

    isint_Bash() { (( 10#$1 )) 2>/dev/null ;}
    
  • 使用 case

    isint_Case() { case ${1#[-+]} in ''|*[!0-9]*) return 1;;esac;}
    
  • 使用 的正则表达式

    isint_Regx() { [[ $1 =~ ^[+-]?[0-9]+$ ]] ;}
    

数字(无符号浮点数)is_num()

这些函数实现代码来评估表达式是否为浮点数,即如上所述,但允许可选的小数点和后面的附加数字。这并不试图涵盖科学记数法中的数字表达式(例如 1.0234E-12)。

  • 使用参数扩展

    isnum_Parm() { local ck=${1#[+-]};ck=${ck/.};[ "$ck" ]&&[ -z "${ck//[0-9]}" ];}
    
  • 使用 的正则表达式

    isnum_Regx() { [[ $1 =~ ^[+-]?([0-9]+([.][0-9]*)?|\.[0-9]+)$ ]] ;}
    
  • 使用 case

    isnum_Case() { case ${1#[-+]} in ''|.|*[!0-9.]*|*.*.*) return 1;; esac ;}
    

概念测试

(您可以在之前声明的函数之后复制/粘贴此测试代码。)

testcases=(
    1 42 -3 +42 +3. .9 3.14 +3.141 -31.4 '' . 3-3 3.1.4 3a a3 blah 'Good day!'
);printf '%-12s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s\n' Function \
       U{Prm,Grp,Bsh,Cse,Rgx} I{Prm,Bsh,Cse,Rgx} N{Prm,Cse,Rgx}; \
for var in "${testcases[@]}";do
    outstr='';
    for func in isuint_{Parm,Grep,Bash,Case,Regx} isint_{Parm,Bash,Case,Regx} \
                       isnum_{Parm,Case,Regx};do
        if $func "$var"
        then outstr+='  num'
        else outstr+='  str'
        fi
    done
    printf '%-11s %s\n' "|$var|" "$outstr"
done

应该输出:

Function     UPrm UGrp UBsh UCse URgx IPrm IBsh ICse IRgx NPrm NCse NRgx
|1|           num  num  num  num  num  num  num  num  num  num  num  num
|42|          num  num  num  num  num  num  num  num  num  num  num  num
|-3|          str  str  str  str  str  num  num  num  num  num  num  num
|+42|         str  str  num  str  str  num  num  num  num  num  num  num
|+3.|         str  str  str  str  str  str  str  str  str  num  num  num
|.9|          str  str  str  str  str  str  str  str  str  num  num  num
|3.14|        str  str  str  str  str  str  str  str  str  num  num  num
|+3.141|      str  str  str  str  str  str  str  str  str  num  num  num
|-31.4|       str  str  str  str  str  str  str  str  str  num  num  num
||            str  str  num  str  str  str  str  str  str  str  str  str
|.|           str  str  str  str  str  str  str  str  str  str  str  str
|3-3|         str  str  num  str  str  str  str  str  str  str  str  str
|3.1.4|       str  str  str  str  str  str  str  str  str  str  str  str
|3a|          str  str  str  str  str  str  str  str  str  str  str  str
|a3|          str  str  str  str  str  str  str  str  str  str  str  str
|blah|        str  str  str  str  str  str  str  str  str  str  str  str
|Good day!|   str  str  str  str  str  str  str  str  str  str  str  str

希望! (注:uint_bash 似乎并不完美!)

性能对比

然后我构建了这个测试函数:

testFunc() {
    local tests=1000 start=${EPOCHREALTIME//.}
    for ((;tests--;)) ;do
        "$1" "$3"
    done
    printf -v "$2" %u $((${EPOCHREALTIME//.}-start))
}
percent(){ local p=00$((${1}00000/$2));printf -v "$3" %.2f%% ${p::-3}.${p: -3};}
sortedTests() {
    local func NaNTime NumTime ftyp="$1" nTest="$2" tTest="$3" min i pct line
    local -a order=()
    shift 3
    for func ;do
        testFunc "${ftyp}_$func" NaNTime "$tTest"
        testFunc "${ftyp}_$func" NumTime "$nTest"
        order[NaNTime+NumTime]=${ftyp}_$func\ $NumTime\ $NaNTime
    done
    printf '%-12s %11s %11s %14s\n' Function Number NaN Total
    min="${!order[*]}" min=${min%% *}
    for i in "${!order[@]}";do
        read -ra line <<<"${order[i]}"
        percent "$i" "$min" pct
        printf '%-12s %9d\U00B5s %9d\U00B5s  %12d\U00B5s  %9s\n' \
               "${line[@]}" "$i" "$pct"
    done
}

我可以这样跑:

sortedTests isuint "This is not a number." 31415926535897932384 \
            Case Grep Parm Bash Regx ;\
sortedTests isint  "This is not a number." 31415926535897932384 \
            Case Parm Bash Regx ;\
sortedTests isnum "This string is clearly not a number..." \
            3.141592653589793238462643383279502884  Case Parm Regx

在我的主机上,这显示:

Function          Number         NaN          Total
isuint_Case       6762µs      8492µs         15254µs    100.00%
isuint_Bash      13478µs     12739µs         26217µs    171.87%
isuint_Parm      11324µs     18807µs         30131µs    197.53%
isuint_Regx      20777µs     27616µs         48393µs    317.25%
isuint_Grep    1516390µs   1491751µs       3008141µs  19720.34%
Function          Number         NaN          Total
isint_Case        8630µs      8042µs         16672µs    100.00%
isint_Bash       14254µs     12272µs         26526µs    159.10%
isint_Parm       16445µs     20491µs         36936µs    221.54%
isint_Regx       23661µs     28287µs         51948µs    311.59%
Function          Number         NaN          Total
isnum_Case        9579µs     10328µs         19907µs    100.00%
isnum_Parm       21115µs     28983µs         50098µs    251.66%
isnum_Regx       35552µs     58453µs         94005µs    472.22%

结论

  • case 方式显然是最快的!比 regex 快大约 3 倍,比使用 参数扩展 快 2 倍。
  • forks(到 grep 或任何二进制文件)在不需要时应避免。

case 方法已成为我的首选:

is_uint() { case $1        in '' | *[!0-9]*              ) return 1;; esac ;}
is_int()  { case ${1#[-+]} in '' | *[!0-9]*              ) return 1;; esac ;}
is_unum() { case $1        in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}
is_num()  { case ${1#[-+]} in '' | . | *[!0-9.]* | *.*.* ) return 1;; esac ;}

关于兼容性

为此,我写了一个little test script based on previous tests,其中:

for shell in bash dash 'busybox sh' ksh zsh "$@";do
    printf "%-12s  " "${shell%% *}"
    $shell < <(testScript) 2>&1 | xargs
done

这表明:

bash          Success
dash          Success
busybox       Success
ksh           Success
zsh           Success

据我所知,其他基于 的解决方案如 regex's integer 赢了不能在许多其他 shell 中工作,并且 forks 资源昂贵,我更喜欢 case 方式 (就在 参数扩展 之前,这也是大部分兼容的)。

【讨论】:

  • 我同意,无论如何,我宁愿不使用正则表达式,当我可以使用参数扩展时......滥用 RE 会使 bash 脚本变慢
  • @CharlesDuffy,在我的树莓派上,我的版本需要 2.5 秒,而你的版本需要 4.4 秒!
  • 你能把case的答案也包括在比较中吗?那个得到了我的投票,既简单又优雅。在我的测试中,它比您的两种选择都快得多。在 IdeOne 上,它不太明显,但速度更快:ideone.com/AVvMOU
  • @tripleee 回答 重写.
  • @tripleee redo test like 发布在那里,很多时候我的办公桌比较安静,然后选择更相关的输出在那里发布。 (你试过我的脚本了吗?我已经添加了一个+Numberr 列,我不会在那里解释这个;)
【解决方案7】:

我对直接在 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' 更改为您需要的任何特定格式。

【讨论】:

  • isnumber(){ printf '%f' "$1" &amp;&gt;/dev/null &amp;&amp; echo "this is a number" || echo "not a number"; }
  • @sputnick 你的版本打破了原始函数固有的(和有用的)返回值语义。因此,只需将函数保持原样并使用它:isnumber 23 &amp;&amp; echo "this is a number" || echo "not a number"
  • 这个不应该也有2&gt;/dev/null,这样isnumber "foo"就不会污染stderr吗?
  • 将现代 shell 称为 bash “用于控制文件和进程的 DSL” 忽略了它们的用途远不止这些 - 一些发行版已经在其上构建了完整的包管理器和 Web 界面(如尽管那可能很丑陋)。批处理文件符合您的描述,因为即使在那里设置变量也很困难。
  • 有趣的是,你试图通过复制其他语言的一些习语来变得聪明。不幸的是,这在 shell 中不起作用。 Shell 非常特殊,如果没有对它们有扎实的了解,您很可能会编写损坏的代码。您的代码已损坏:isnumber "'a" 将返回 true。这在POSIX spec 中有记录,您将在其中阅读:如果前导字符是单引号或双引号,则该值应是单引号后面字符的基础代码集中的数值或双引号。
【解决方案8】:

我正在查看答案,然后... 意识到没有人考虑浮点数(带点)!

使用 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

要匹配 JUST 整数,请使用:

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

【讨论】:

  • triple_r 使用 awk 和 Tripleee 使用浮点数的解决方案。
  • 谢谢你,非常好的一点!因为问题实际上是如何检查它是否是一个 number 而不仅仅是一个整数。
  • 我也感谢塔纳西斯!让我们永远互相帮助。
【解决方案9】:

只是对@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

【讨论】:

  • 打印数字不如设置退出代码有用。 'BEGIN { exit(1-(a==a+0)) }' 有点难以理解,但可以在返回 true 或 false 的函数中使用,就像 [grep -q 等。
【解决方案10】:
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 部分竖起大拇指。每天学习新东西。 :)
  • @sjas 您可以在正则表达式中轻松添加\- 来解决该问题。使用[0-9\-\.\+] 计算浮点数和有符号数。
  • @sjas 好吧,我的错
  • @sjas echo $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' &amp;&amp; echo yes || echo no
【解决方案11】:

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

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

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

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

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

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

【讨论】:

  • 这是错误的(错误)!试试这个:foo=1;set -- foo;(( $1 )) 2&gt;/dev/null &amp;&amp; echo "'$1' is a number"
【解决方案12】:

这可以通过使用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-9 零到九的 [],并且至少应包含 + 一个字符。
  • grep-q quiet 开关会关闭任何输出,无论它是否找到任何东西。
  • if 检查grep 的退出状态。退出状态0 表示成功,任何更大的都表示错误。 grep 命令如果找到匹配项,则退出状态为 0,如果没有,则退出状态为 1

所以把它们放在一起,在if 测试中,我们将echo 变量$yournumber| 用管道传递给grep-q 开关与-E 扩展正则匹配表达式'^[0-9]+$' 表达式。如果grep 成功找到匹配项,则grep 的退出状态将为0,否则为1。如果匹配成功,我们echo "Valid number."。如果匹配失败,我们echo "Error: not a number."


对于浮点数或双精度数

我们可以将正则表达式从 '^[0-9]+$' 更改为 '^[0-9]*\.?[0-9]+$' 用于浮点数或双精度数。

测试浮点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]+$'

【讨论】:

  • LGTM;编辑后的答案有我的+1。在这一点上我唯一会做的不同的事情只是意见问题而不是正确性(f / e,使用[-]而不是\-[.]而不是\.有点冗长,但它意味着如果您的字符串在使用反斜杠的上下文中使用,则不必更改)。
  • 我在旧的基于 Ubuntu 14.04 的系统中使用了与 if [[ $yournumber =~ ^[0-9]+([.][0-9]+)?$ ]] ; then 不同的方法,但不知何故,它在升级到 Ubuntu 20.04 后停止工作,你的第一个“测试整数”解决方案在20.04。我不能说它是否与升级有关,或者我的脚本在第一个实例中是错误的并且 - 不知何故 - 在旧系统中工作。非常感谢。
  • @GeppettvsD'Constanzo,也许脚本一直在使用#!/bin/sh?如果是这样,只要您使用 #!/bin/bash shebang,它应该仍然可以在现代 Ubuntu 中工作,并避免使用 sh scriptname 启动脚本(这会忽略 shebang 并强制使用 sh 而不是 bash)。跨度>
  • 对 Bash 内置的东西使用外部进程总是可疑的。
【解决方案13】:

老问题,但我只是想补充一下我的解决方案。这个不需要任何奇怪的 shell 技巧,也不需要依赖一些从未存在过的东西。

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

基本上它只是从输入中删除所有数字,如果你留下一个非零长度的字符串,那么它就不是一个数字。

【讨论】:

  • 这对于空的var 失败。
  • 或者对于带有尾随换行符或类似$'0\n\n\n1\n\n\n2\n\n\n3\n'的变量。
  • 要求 多个 外部进程来处理 shell 完全能够使用纯内置函数处理的东西只是不好的做法。
【解决方案14】:

我会试试这个:

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

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

【讨论】:

  • pixelbeat 的答案的副本,或者更适合作为评论(使用%f 可能更好)
  • 不检查之前的状态码,为什么不把它放在if本身呢?这就是 if 所做的... if printf "%g" "$var" &amp;&gt; /dev/null; then ...
  • 这还有其他注意事项。它将验证空字符串,以及像'a 这样的字符串。
  • 最佳解决方案,在我的书中。在意识到 bc 不做浮点数之前,我尝试了 bc 。将空字符串解释为数字是一个小警告(并且“a”不被解释为数字)。
  • @JPGConnly,“bc 不做浮点数”是什么意思?
【解决方案15】:

还不能发表评论,所以我将添加我自己的答案,这是对 glenn jackman 使用 bash 模式匹配的答案的扩展。

我最初的需要是识别数字并区分整数和浮点数。扣除的函数定义为:

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))。我的单元测试只测试整数或浮点值,而不是任何无效输入。

【讨论】:

    【解决方案16】:

    @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
    

    虽然对于实数来说,radix point 之前不是必须要有数字。

    为了提供对浮点数和科学记数法的更全面支持(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
    

    【讨论】:

      【解决方案17】:
      [[ $1 =~ ^-?[0-9]+$ ]] && echo "number"
      

      不要忘记- 包含负数!

      【讨论】:

      • bash 的最低版本是多少?我刚刚得到 bash:条件二元运算符预期 bash:意外标记 `=~' 附近的语法错误
      • @PaulHargreaves =~ 至少可以追溯到 bash 3.0。
      • @PaulHargreaves 你的第一个操作数可能有问题,例如太多的引号或类似的
      • @JoshuaClayton 我询问了版本,因为它在 Solaris 7 机器上是非常非常旧的 bash,我们仍然拥有它,但它不支持 =~
      【解决方案18】:

      我使用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 不为零。

      【讨论】:

      • 这很糟糕。为了让它稍微好一点,你应该使用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,
      • 有了 Bash,你真的不需要expr。如果你被限制在像 POSIX sh 这样较小的 Bourne shell,那么也许吧。
      【解决方案19】:

      因为我最近不得不篡改这一点,并且最喜欢 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 关键字更多是个人风格的问题,至少我将它们从一个衬里中删除以节省一些空间。谢谢。
      • 不要忘记单行版本测试后需要分号。
      • isNumber 将在任何包含数字的字符串上返回“true”。
      • @DrStrangepork 确实,我的 false_values 数组缺少这种情况。我会调查的。感谢您的提示。
      【解决方案20】:

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

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

      【讨论】:

      • 处理负数需要更复杂的方法。
      • ... 或可选的正号。
      • @tripleee 如果你知道怎么做,我想看看你的方法。
      【解决方案21】:

      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 允许您执行“0xAZ00”之类的操作,并且仍然将其计为一个数字(这不是来自 GCC 编译器的一些奇怪的怪癖,它允许您对 16 以外的基数使用 0x 表示法吗? )

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

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

      【讨论】:

      • 如果变量包含一个数字,[[ $VAR = *[[:digit:]]* ]] 将返回真,如果它一个整数则不会。
      • [[ "z3*&amp;" = *[[:digit:]]* ]] &amp;&amp; echo "numeric" 打印出numeric。在 bash 版本 3.2.25(1)-release 中测试。
      • @ultraswadable,您的解决方案检测到那些至少包含一个数字的字符串,这些数字被任何其他字符包围(或不包围)。我投了反对票。
      • 显然正确的方法是扭转这一点,并使用[[ -n $VAR &amp;&amp; $VAR != *[^[:digit:]]* ]]
      • @eschwartz ,您的解决方案不适用于负数
      【解决方案22】:

      我使用 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
      } 
      

      &gt;$ var=45

      &gt;$ (($(check_number $var "${CHECK_INTEGER}" "Error: Found $var - An integer is required."))) &amp;&amp; { echo "$var+5" | bc; }

      【讨论】:

        【解决方案23】:

        我喜欢 Alberto Zaccagni 的回答。

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

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

        但如果$var 很复杂(例如,关联数组访问),并且数字是非负整数(大多数用例),那么这可能更有效?

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

        【讨论】:

        • 这不仅对复数(具有虚数部分)失败,而且对浮点数(非整数部分)也失败。
        【解决方案24】:

        你也可以像这样使用“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+ 运算符,就像这个线程中的一些答案一样。

        【讨论】:

        • 这很危险。不要在 shell 中评估未经验证的算术。必须先通过其他方式对其进行验证。
        • @ormaaj 为什么它很危险?如恶意号码,或溢出?当输入是你自己的值时会不会很危险?
        【解决方案25】:

        在语法上几乎如你所愿。只需要一个函数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 
        

        【讨论】:

          【解决方案26】:

          捕捉负数:

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

          【讨论】:

          • 另外,这需要先启用扩展通配符。这是一个仅限 Bash 的功能,默认情况下是禁用的。
          • @tripleee 扩展通配符在使用 == 或 != 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 时自动激活
          • @BadrElmers 感谢您的更新。这似乎是我的 Bash 3.2.57 (MacOS Mojave) 中不正确的新行为。我看到它像你在 4.4 中描述的那样工作。
          【解决方案27】:
          printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."
          

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

          【讨论】:

          • 因缺乏解释而投反对票。这是如何运作的?它看起来复杂而脆弱,而且它究竟会接受什么输入并不明显。 (例如,删除空格是否非常必要?为什么?它会说带有嵌入空格的数字是有效数字,这可能是不可取的。)
          【解决方案28】:

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

          re="^[0-9]*[.]{0,1}[0-9]*$"
          
          if [[ $1 =~ $re ]] 
          then
             echo "is numeric"
          else
            echo "Naahh, not numeric"
          fi
          

          【讨论】:

          • 您能否解释一下为什么您的答案与其他旧答案(例如 Charles Duffy 的答案)根本不同?好吧,您的答案实际上是错误的,因为它验证了一个周期 .
          • 不确定是否理解这里的单个句点...预计是一个或零个句点...但是没有根本不同,只是发现正则表达式更易于阅读。
          • 也使用 * 应该匹配更多真实世界的案例
          • 问题是你匹配空字符串a='' 和只包含句点的字符串a='.' 所以你的代码有点坏了......
          【解决方案29】:

          我使用以下(整数):

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

          【讨论】:

          • 几乎是正确的(您接受的是空字符串),但非常复杂到令人困惑的程度。
          • 不正确:您接受-n 等(因为echo),并且您接受带有尾随换行符的变量(因为$(...))。顺便说一句,print 不是有效的 shell 命令。
          【解决方案30】:

          我尝试了超锯刃的配方,因为它对我来说似乎最实用,但无法奏效。最后,我设计了另一种方法,基于参数替换的其他方法,这次使用正则表达式替换:

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

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

          我喜欢这个的是它的小尺寸和灵活性。在这种形式中,它仅适用于非定界的、以 10 为基数的整数,但您当然可以使用模式匹配来满足其他需求。

          【讨论】:

          • 阅读 mrucci 的解决方案,它看起来和我的几乎一样,但使用常规字符串替换而不是“sed 样式”。两者都使用相同的模式匹配规则,并且是 AFAIK 可互换的解决方案。
          • sed 是 POSIX,而您的解决方案是 bash。各有各的用途
          猜你喜欢
          • 2011-05-07
          • 1970-01-01
          • 2012-06-09
          • 2012-07-06
          • 1970-01-01
          • 1970-01-01
          • 2016-07-27
          相关资源
          最近更新 更多