ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我想做的就是这样的:

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
您需要更具体地说明“数字”的含义。一个整数?定点数?科学(“e”)符号?是否有要求的范围(例如 64 位无符号值),或者您是否允许任何可以写入的数字?

C
Charles Duffy

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

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 版本中都是不变的。因此,遵循在使用 =~ 匹配变量之前始终将正则表达式分配给变量的做法可以避免意外。我这样做是为了教导良好的习惯,即使这个特殊的正则表达式没有反斜杠转义。
j
jilles

没有 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 模式的扩展可能需要引用,因为它决定了模式匹配字符是文字还是特殊字符。)
管道(竖线)字符的解释:unix.stackexchange.com/questions/85939/…
g
glenn jackman

以下解决方案也可用于基本的 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 打印出来的“预期整数表达式”消息,以防我们没有数字。

注意事项(感谢以下评论):

带小数点的数字未被识别为有效的“数字”

使用 [[ ]] 而不是 [ ] 将始终评估为 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 而不会破坏任何记录在案的行为承诺,因此不能保证依赖其当前行为持续存在是安全的。
g
glenn jackman

没有人建议 bash 的 extended pattern matching

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

或使用 a POSIX character class

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

Glenn,我从您的帖子中删除了 shopt -s extglob(我赞成,这是我最喜欢的答案之一),因为在 Conditional Constructs 中您可以阅读:当使用 ==!= 运算符时,运算符右侧的字符串被视为一个模式,并根据以下 Pattern Matching 中描述的规则进行匹配,就像启用了 extglob shell 选项一样。 希望您不要介意!
@Jdamian:你说得对,这是在 Bash 4.1 中添加的(它于 2009 年底发布……Bash 3.2 于 2006 年发布……它现在是一个古董软件,对于那些被困在过去的人感到抱歉)。此外,您可能会争辩说 extglobs 是在 2.02 版(1998 年发布)中引入的,并且在 <2.02 版本中不起作用……现在您在此处的评论将作为对旧版本的警告。
[[...]] 中的变量不受分词或全局扩展的影响。
@ThiagoConrado,在手册中查找 [[...]](或在 bash 提示符下查找 help [[):只有 == 的右侧是一个模式。
对于 POSIX,使用 [[:digit:]] 而不是 [:digit:]
m
mrucci

这会测试一个数字是否为非负整数。它独立于 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”都不是数字。
F
F. Hauri - Give Up GitHub

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

一些性能和兼容性提示

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

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

无符号整数 is_uint()

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

使用参数扩展(这是我之前的方法!) isuint_Parm() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}

使用 fork grep isuint_Grep() { grep -qE '^[0-9]+$' <<<"$1";我只测试这个方法一次,因为它很慢。这只是为了显示不该做什么。

使用 bash 整数功能 isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}

使用 case isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}

使用 bash 的正则表达式 isuint_Regx() { [[ $1 =~ ^[0-9]+$ ]] ;}

有符号整数 is_int()

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

使用参数扩展 isint_Parm() { local chk=${1#[+-]}; [ "$chk" ] && [ -z "${chk//[0-9]}" ] ;}

使用 bash 整数功能 isint_Bash() { (( 10#$1 )) 2>/dev/null ;}

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

使用 bash 的正则表达式 isint_Regx() { [[ $1 =~ ^[+-]?[0-9]+$ ]] ;}

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

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

使用参数扩展 isnum_Parm() { local ck=${1#[+-]};ck=${ck/.};[ "$ck" ]&&[ -z "${ck//[0-9] }" ];}

使用 bash 的正则表达式 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        Rank
isuint_Case         8,080µs        6,848µs         14,928µs     100.00%
isuint_Parm        10,571µs       13,061µs         23,632µs     158.31%
isuint_Regx        12,865µs       15,407µs         28,272µs     189.39%
isuint_Bash        19,054µs       17,182µs         36,236µs     242.74%
isuint_Grep     1,333,786µs    1,416,626µs      2,750,412µs   18424.52%

Function             Number            NaN            Total        Rank
isint_Case          8,860µs        7,813µs         16,673µs     100.00%
isint_Parm         14,141µs       16,774µs         30,915µs     185.42%
isint_Regx         14,202µs       17,375µs         31,577µs     189.39%
isint_Bash         18,988µs       16,598µs         35,586µs     213.43%

Function             Number            NaN            Total        Rank
isnum_Case          8,935µs        9,232µs         18,167µs     100.00%
isnum_Parm         18,898µs       22,577µs         41,475µs     228.30%
isnum_Regx         25,336µs       42,825µs         68,161µs     375.19%

您可以下载 full isnum comparission script herefull isnum comparission script as text here.,(使用 UTF8 和 LATIN 处理)。

结论

案例方式显然是最快的!比正则表达式快 3 倍,比使用参数扩展快 2 倍。

不需要时应避免分叉(到 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 的整数 等其他基于 的解决方案将不起作用在许多其他 shell 中,forks 资源昂贵,我更喜欢 case 方式(就在 参数扩展之前 也大多兼容)。


我同意,无论如何,当我可以使用参数扩展时,我宁愿不使用正则表达式......滥用 RE 会使 bash 脚本变慢
您能否在比较中也包括 case 答案?那个得到了我的投票,既简单又优雅。在我的测试中,它比您的两种选择都快得多。在 IdeOne 上,它不太明显,但速度更快:ideone.com/AVvMOU
@tripleee 答案重写。
@tripleee 重做那里发布的测试,很多时候我的办公桌相对安静,然后选择更相关的输出在那里发布。 (您尝试过我的脚本吗?我添加了一个 +Numberr 列,我不会尝试在那里解释这一点;)
基于这些解决方案,您可以扩展到包含科学记数法的全浮点比较:is_float() { is_num "${1/[eE][-+]/}"; }
p
pixelbeat

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

我正在查看答案并且......意识到没有人考虑过浮点数(带点)!

使用 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 和 Tripleee 使用 awk 的解决方案适用于浮点数。
感谢这一点,非常好的一点!因为问题实际上是如何检查它是否是一个数字而不仅仅是一个整数。
我也感谢塔纳西斯!让我们永远互相帮助。
g
gniourf_gniourf

只是对@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 等。
u
user2683246
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)' && echo yes || echo no
H
Hachi

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

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

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

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

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

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

这是错误的(错误的)!试试这个:foo=1;set -- foo;(( $1 )) 2>/dev/null && echo "'$1' is a number"
J
Joseph Shih

这可以通过使用 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| 通过管道将它与 -q 开关匹配 -E 扩展正则表达式 {8 } 表达。如果 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 内置的东西使用外部进程总是令人怀疑的。
g
gniourf_gniourf

老问题,但我只是想解决我的解决方案。这个不需要任何奇怪的 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 完全能够使用纯内置处理的东西只是不好的做法。
o
overflowed

我会试试这个:

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" &> /dev/null; then ...
这还有其他警告。它将验证空字符串和像 'a 这样的字符串。
最好的解决方案,在我的书中。在意识到 bc 不做浮点数之前,我尝试了 bc 。将空字符串解释为数字是一个小警告(并且“a”不被解释为数字)。
@JPGConnly,你是什么意思“bc 不做花车”?
k
karttu

还不能发表评论,所以我将添加我自己的答案,这是对 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)) 更加宽容。我的单元测试仅测试整数或浮点值,但不测试任何无效输入。


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

不要忘记 - 包括负数!


bash 的最低版本是多少?我刚刚得到 bash:条件二元运算符预期 bash:意外标记 `=~' 附近的语法错误
@PaulHargreaves =~ 至少可以追溯到 bash 3.0。
@PaulHargreaves您的第一个操作数可能有问题,例如引号过多或类似
@JoshuaClayton 我询问了这个版本,因为它是 Solaris 7 机器上非常非常古老的 bash,我们仍然拥有它并且它不支持 =~
B
BobDodds

@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

D
David W.

我使用 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,那么也许可以。
POSIX sh 保证有 $(( ))。您说的是 1970 年代的伯恩需要 expr
r
roaima

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

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

处理负数需要更复杂的方法。
...或可选的正号。
@tripleee 如果您知道该怎么做,我想看看您的方法。
@andrew 有了这个小小的改变,你的代码(使用 zsh)工作得很好!即使对于负数或正数:[[ ! -n ${1//[+\-0-9]/} ]] && echo "is a number" || echo "is not a number";。现在的问题是 +-123 也会通过。
最后,从您的回答开始,我使用更多更改达到了预期的结果。希望能帮助更多的人。 gist.github.com/bernardolm/1c9e003a5f68e6e2534458fa758a096d
u
ultrasawblade

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:]]* ]] 将返回 true,而不是如果它一个整数。
[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric" 打印 numeric。在 bash 版本 3.2.25(1)-release 中测试。
@ultraswadable,您的解决方案检测到那些至少包含一个数字被任何其他字符包围(或不包围)的字符串。我投了反对票。
因此,显然正确的方法是扭转这种情况,并使用 [[ -n $VAR && $VAR != *[^[:digit:]]* ]]
@eschwartz,您的解决方案不适用于负数
3
3ronco

因为我最近不得不篡改这个并且最喜欢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 任何操作。
很好,确实 return 令人困惑,使其可读性降低。是否使用 function 关键字更多是个人风格的问题,至少我将它们从一个衬里中删除以节省一些空间。谢谢。
不要忘记在单行版本的测试之后需要分号。
isNumber 将在任何包含数字的字符串上返回“true”。
@DrStrangepork 确实,我的 false_values 数组缺少这种情况。我会调查的。感谢您的提示。
佚名

我使用 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}" "Error: Found $var - An integer is required."))) && { echo "$var+5" | bc; }


s
shilovk

我喜欢 Alberto Zaccagni 的回答。

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

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

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

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

这不仅适用于复数(具有虚部的那些),而且适用于浮点数(具有非整数成分的那些)。
u
user28490

要捕获负数:

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

此外,这需要首先启用扩展的通配符。这是一个仅限 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 时会自动激活 @tripleee 扩展通配符
@BadrElmers 感谢您的更新。这似乎是我的 Bash 3.2.57 (MacOS Mojave) 中不正确的新行为。我看到它像您在 4.4 中描述的那样工作。
I
Idriss Neumann

你也可以像这样使用“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 为什么它很危险?如恶意号码,或溢出?当输入是您自己的值时是否危险?
D
Daniil Loban

几乎就像你想要的语法一样。只需要一个函数 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 

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

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


因缺乏解释而投反对票。这是如何运作的?它看起来复杂而脆弱,而且它究竟会接受什么输入并不明显。 (例如,删除空格是否至关重要?为什么?它会说带有嵌入空格的数字是有效数字,这可能是不可取的。)
J
Jerome

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

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

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

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

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

## ##### 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 命令。
a
ata

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

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

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

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


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