测试字符串是否为有效整数


117

我正在尝试做一些足够普通的事情:在shell脚本中解析用户输入。如果用户提供了一个有效的整数,则脚本将执行一件事,如果无效,则脚本将执行其他操作。麻烦的是,我还没有找到一种简便(且相当优雅)的方法-我不想将它逐个字符地分开。

我知道这一定很容易,但我不知道如何。我可以用十几种语言来做,但是不能用BASH!

在我的研究中,我发现:

用于测试字符串是否以10为底的有效实数组成的正则表达式

在其中有一个讨论正则表达式的答案,但据我所知,这是C语言中可用的功能(以及其他功能)。尽管如此,它看起来还是一个不错的答案,所以我尝试使用grep进行尝试,但是grep不知道该怎么做。我尝试了-P,这意味着将其视为PERL regexp-nada。破折号E(-E)也不起作用。而且-F也没有。

为了清楚起见,我正在尝试类似的事情,寻找任何输出-从那里,我会整理脚本以利用我得到的一切。(IOW,我期望在重复有效行时,不合格的输入不返回任何内容。)

snafu=$(echo "$2" | grep -E "/^[-+]?(?:\.[0-9]+|(?:0|[1-9][0-9]*)(?:\.[0-9]*)?)$/")
if [ -z "$snafu" ] ;
then
   echo "Not an integer - nothing back from the grep"
else
   echo "Integer."
fi

请问有人说明这是最容易做到的吗?

坦率地说,我认为这是TEST的缺点。它应该有这样的标志

if [ -I "string" ] ;
then
   echo "String is a valid integer."
else
   echo "String is not a valid integer."
fi

4
仅供参考:[与旧版本兼容test[[是Bash的新事物,它具有更多的操作和不同的报价规则。如果您已经决定坚持使用Bash,那就去吧[[(确实更好);如果您需要移植到其他外壳,请[[完全避免。
短暂

Answers:


183
[[ $var =~ ^-?[0-9]+$ ]]
  • ^表示输入模式的开始
  • -是字符“ - ”
  • ?意思是“0或前述的1(-)”
  • +意思是“1或多个前述的([0-9])”
  • $指示输入图案的端部

因此,正则表达式匹配一个可选的-(对于负数),后跟一个或多个十进制数字。

参考文献


3
感谢Ignacio,我将在稍后尝试。您介意解释一下,以便我学习一些吗?我收集到这样的信息:“在字符串(^)的开头,减号(-)是可选的(?),后跟零至9之间的任意数量的字符”(包括+),然后+ $是什么意思?谢谢。
理查德T 2010年

10
+意思是“1或多个前述的”,并且$指示输入图案的端部。因此,正则表达式匹配一个可选的,-后面跟一个或多个十进制数字。
伊格纳西奥·巴斯克斯

抱怨:ABS链接
查尔斯·达菲

这是一个切线,但是请注意,在指定字符范围时,您会得到奇怪的结果。例如,[A-z]将不仅给你A-Za-z\ []^_,和`
Doktor J '18

此外,基于字符排序规则(请参见此相关问题/答案),类似的d[g-i]{2}结果不仅会匹配,dig而且还会dish导致该答案建议的排序规则中出现(在此,sh有向图被认为是单个字符,之后进行排序h)。
Doktor J '18

61

哇...这里有很多好的解决方案!在上述所有解决方案中,我完全同意@使用-eq一根衬垫是最酷的。

我正在运行GNU bash版本4.1.5(Debian)。我也在ksh(SunSO 5.10)上进行了检查。

这是我检查是否$1为整数的版本:

if [ "$1" -eq "$1" ] 2>/dev/null
then
    echo "$1 is an integer !!"
else
    echo "ERROR: first parameter must be an integer."
    echo $USAGE
    exit 1
fi

该方法还考虑了负数,其他一些解决方案将产生错误的负数,并且将允许前缀“ +”(例如+30),这显然是整数。

结果:

$ int_check.sh 123
123 is an integer !!

$ int_check.sh 123+
ERROR: first parameter must be an integer.

$ int_check.sh -123
-123 is an integer !!

$ int_check.sh +30
+30 is an integer !!

$ int_check.sh -123c
ERROR: first parameter must be an integer.

$ int_check.sh 123c
ERROR: first parameter must be an integer.

$ int_check.sh c123
ERROR: first parameter must be an integer.

解释完之后,Ignacio Vazquez-Abrams提供的解决方案也非常简洁(如果您喜欢正则表达式)。但是,它不处理带有+前缀的正数,但可以很容易地按以下方式进行固定:

[[ $var =~ ^[-+]?[0-9]+$ ]]

真好!与此非常相似。
devnull

是。这是相似的。但是,我一直在为“ if”语句寻找统一的解决方案。我以为我真的不需要为此调用一个函数。另外,我可以看到在函数中将stderr重定向到stdout。当我尝试时,显示stderr消息“预期为整数表达式”,这对我而言并不理想。
何鸿Peter

谢谢!我称其为轻松优雅。
Ezra Nugroho 2015年

2
您的解决方案与正则表达式之间有一个明显的区别:整数的大小是根据bash限制检查的(在我的计算机上是64位)。此限制不会达到regexp解决方案。因此,在64位计算机上,您的解决方案将在数量上严格大于922337203685477575807时失败。
vaab

2
我最近发现,有一些警告
凯尔·斯特兰德

28

晚会来这里了。令我惊讶的是,没有一个答案提到最简单,最快,最便携的解决方案。该case声明。

case ${variable#[-+]} in
  *[!0-9]* | '') echo Not a number ;;
  * ) echo Valid number ;;
esac

在比较之前对任何符号进行修整都感觉有点不合时宜,但这使case语句的表达式变得更加简单。


4
我希望我每次因受骗而回到这个问题时都可以投票赞成。一个简单而又兼容POSIX的解决方案被埋在了底部,这使我不寒而栗。
AdrianFrühwirth2014年

3
也许您应该注意空字符串:''|*[!0-9]*)
Niklas Peter

2
顺便说一句:这是此语法的文档:tldp.org/LDP/abs/html/string-manipulation.html
Niklas Peter

我并不特别宽容ABS。这显然也记录在Bash手册中。无论如何,您链接到的部分并没有描述这个特定的结构,而是例如@Nortally的答案。
2015年

@tripleee链接的文档描述了用于从case行中使用的变量中删除字符串前缀的构造。它只是在页面的底部,但是没有锚,所以我无法直接链接到它,请参阅“子字符串删除”部分
Niklas Peter

10

我喜欢使用的解决方案 -eq测试,因为它基本上是单线的。

我自己的解决方案是使用参数扩展丢弃所有数字,然后查看是否还有剩余。(我现在还在用3.0,没有使用过[[expr之前,但很高兴见到他们。)

if [ "${INPUT_STRING//[0-9]}" = "" ]; then
  # yes, natural number
else
  # no, has non-numeral chars
fi

4
可以使用[ -z "${INPUT_STRING//[0-9]}" ]非常好的解决方案进一步改善这一点!
ShellFish 2015年

负号呢?
scottysseus

-eq解决方案存在一些问题。看到这里:stackoverflow.com/a/808740/1858225
凯尔·斯特兰德

空INPUT_STRING被视为数字,因此我的情况不对
Manwe

9

为了可移植到Bash 3.1之前的版本(=~引入测试时),请使用expr

if expr "$string" : '-\?[0-9]\+$' >/dev/null
then
  echo "String is a valid integer."
else
  echo "String is not a valid integer."
fi

expr STRING : REGEX搜索在STRING开头锚定的REGEX,回显第一组(或匹配长度,如果没有匹配的话)并返回成功/失败。这是旧的regex语法,因此多余\-\?表示“也许-”,[0-9]\+表示“一个或多个数字”,$表示“字符串结尾”。

Bash还支持扩展的glob,尽管我不记得从哪个版本开始。

shopt -s extglob
case "$string" of
    @(-|)[0-9]*([0-9]))
        echo "String is a valid integer." ;;
    *)
        echo "String is not a valid integer." ;;
esac

# equivalently, [[ $string = @(-|)[0-9]*([0-9])) ]]

@(-|)表示“ -或没有”,[0-9]表示“数字”和*([0-9])“零个或多个数字”。


谢谢你,我很荣幸。我以前从未见过=〜语法-仍然不知道它的含义-大致相等?...我从来没有为使用BASH编程感到兴奋,但是必要这样做!
理查德T 2010年

在中awk~是“正则表达式匹配”运算符。在Perl(从C语言复制)中,~已经用于“位补码”,因此他们使用=~。后来的注释被复制到其他几种语言。(Perl 5.10和Perl 6更像这样~~,但这对这里没有影响。)我想您可以将其视为某种近似相等...
ephemient 2010年

优秀的帖子并编辑!我非常感谢您解释其含义。希望我能将您和Ignacio的帖子都标记为正确答案。-皱眉-你们俩都很棒。但是,由于您的声誉倍增,我将其授予Ignacio-希望您能理解!-smile-
Richard T

4

这是它的另一种用法(仅使用test内置命令及其返回代码):

function is_int() { return $(test "$@" -eq "$@" > /dev/null 2>&1); } 

input="-123"

if $(is_int "${input}");
then
   echo "Input: ${input}"
   echo "Integer: $[${input}]"
else
   echo "Not an integer: ${input}"
fi

1
这是没有必要使用$()if。这工作:if is_int "$input"。此外,$[]不建议使用该表单。使用$(())代替。可以在其中任何一个中省略美元符号:echo "Integer: $((input))"不需要在脚本中的任何位置使用大括号。
暂停,直到另行通知。

我希望它也可以将Bash基本表示法中的数字作为有效整数来处理(当然,根据某种定义,它们是有效的整数;但是可能与您的观点不同),但test似乎并不支持这一点。 [[但是。 [[ 16#aa -eq 16#aa ]] && echo integer打印“整数”。
三胞胎

注意,[[此方法返回假阳性。例如[[ f -eq f ]]成功。因此,必须使用test[
分拆

3

您可以去除非数字并进行比较。这是一个演示脚本:

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09"
do
    match=${num//[^[:digit:]]}    # strip non-digits
    match=${match#0*}             # strip leading zeros
    echo -en "$num\t$match\t"
    case $num in
        $match|-$match)    echo "Integer";;
                     *)    echo "Not integer";;
    esac
done

这是测试输出的样子:

44 44整数
-44 44整数
44- 44不是整数
4-4 44不是整数
a4 4不是整数
4a 4不是整数
.4 4不是整数
4.4 44不是整数
-4.4 44不是整数
09 9不是整数

丹尼斯,您好:感谢您向我介绍上面match =右边的语法。我以前从未注意到过这种类型的语法。我认识到tr的一些语法(我还不太熟练的实用程序,但有时会摸不着头脑);我在哪里可以读到这种语法?(即,这种东西叫什么?)谢谢。
理查德T 2010年

您可以在Bash手册页的“参数扩展”部分中查找有关的信息${var//string}${var#string}并在[^ [:digit:]]`的“模式匹配”部分中进行查找(也已在中进行了介绍man 7 regex)。
暂停,直到另行通知。

1
match=${match#0*}没有剥离前导零,它去除至多一个零。使用扩展只能使用extglobvia 来实现match=${match##+(0)}
AdrianFrühwirth'14

9或09是整数吗?
Mike Q

@MikeQ:09如果您认为整数不包含前导零,则不是整数。测试是输入(09)是否等于已清理版本(9-整数),否。
暂停,直到另行通知。

2

对我来说,最简单的解决方案是在(())表达式中使用变量,如下所示:

if ((VAR > 0))
then
  echo "$VAR is a positive integer."
fi

当然,仅当零值对您的应用没有意义时,此解决方案才有效。就我而言,这确实是事实,这比其他解决方案要简单得多。

正如在评论中指出,这可以使你受到代码执行攻击:该(( ))运算符计算VAR,如在规定Arithmetic Evaluation的部分 bash(1)手册页。因此,当VAR不确定内容的来源时,您不应使用此技术(当然,也不应使用任何其他形式的变量扩展)。


您甚至可以通过if (( var )); then echo "$var is an int."; fi
Aaron R.

2
但这对于负整数@aaronr也将返回true,而不是OP所寻找的东西。
Trebor Rude 2014年

2
这很危险,请参见:n = 1; var =“ n”; 如果((var)); 然后回显“ $ var是一个整数”。fi
jarno'1

2
这是一个非常糟糕的主意,并且会受到任意代码执行的影响:请自己尝试:VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi。在这一点上,您很高兴我没有输入任何邪恶命令代替ls。因为OP提到了用户输入,所以我确实希望您不要在生产代码中将它与用户输入一起使用!
gniourf_gniourf 2015年

如果字符串中包含某些数字,则该方法将agent007
无效

1

或使用sed:

   test -z $(echo "2000" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # integer

   test -z $(echo "ab12" | sed s/[0-9]//g) && echo "integer" || echo "no integer"
   # no integer

在Bash和其他一些“ Bourne plus” shell中,可以避免使用test -z "${string//[0-9]/}" && echo "integer" || echo "no integer"... 替换命令和使用外部命令,尽管这基本上重复了Dennis Williamson的回答
Tripleee

谢谢!唯一在这里有效的答案!
用户

无声替代:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
用户

0

加上伊格纳西奥·巴斯克斯(Ignacio Vazquez-Abrams)的答案。这将允许+号在整数之前,并且将允许任意数量的零作为小数点。例如,这将允许+45.00000000被视为整数。
但是,$ 1必须格式化为包含小数点。45不是整数,而是45.0。

if [[ $1 =~ ^-?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
elif [[ $1 =~ ^\+?[0-9]+.?[0]+$ ]]; then
    echo "yes, this is an integer"
else
    echo "no, this is not an integer"
fi

您是否有理由对正数和负数使用两个不同的正则表达式,而不是^[-+]?[0-9]...?
点钟

0

为了大笑,我大概只是​​快速地设计出一组函数来做到这一点(is_string,is_int,is_float,alpha字符串或其他),但是有更有效的方法(更少的代码)来做到这一点:

#!/bin/bash

function strindex() {
    x="${1%%$2*}"
    if [[ "$x" = "$1" ]] ;then
        true
    else
        if [ "${#x}" -gt 0 ] ;then
            false
        else
            true
        fi
    fi
}

function is_int() {
    if is_empty "${1}" ;then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^0-9]*//g')
    if [[ $tmp == "${1}" ]] || [[ "-${tmp}" == "${1}" ]] ; then
        #echo "INT (${1}) tmp=$tmp"
        true
    else
        #echo "NOT INT (${1}) tmp=$tmp"
        false
    fi
}

function is_float() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if ! strindex "${1}" "-" ; then
        false
        return
    fi
    tmp=$(echo "${1}" | sed 's/[^a-z. ]*//g')
    if [[ $tmp =~ "." ]] ; then
        #echo "FLOAT  (${1}) tmp=$tmp"
        true
    else
        #echo "NOT FLOAT  (${1}) tmp=$tmp"
        false
    fi
}

function is_strict_string() {
    if is_empty "${1}" ;then
        false
        return
    fi
    if [[ "${1}" =~ ^[A-Za-z]+$ ]]; then
        #echo "STRICT STRING (${1})"
        true
    else
        #echo "NOT STRICT STRING (${1})"
        false
    fi
}

function is_string() {
    if is_empty "${1}" || is_int "${1}" || is_float "${1}" || is_strict_string "${1}" ;then
        false
        return
    fi
    if [ ! -z "${1}" ] ;then
        true
        return
    fi
    false
}
function is_empty() {
    if [ -z "${1// }" ] ;then
        true
    else
        false
    fi
}

在这里运行一些测试,我定义-44是一个int,但44-不是int。

for num in "44" "-44" "44-" "4-4" "a4" "4a" ".4" "4.4" "-4.4" "09" "hello" "h3llo!" "!!" " " "" ; do
    if is_int "$num" ;then
        echo "INT = $num"

    elif is_float "$num" ;then
        echo "FLOAT = $num"

    elif is_string "$num" ; then
        echo "STRING = $num"

    elif is_strict_string "$num" ; then
        echo "STRICT STRING = $num"
    else
        echo "OTHER = $num"
    fi
done

输出:

INT = 44
INT = -44
STRING = 44-
STRING = 4-4
STRING = a4
STRING = 4a
FLOAT = .4
FLOAT = 4.4
FLOAT = -4.4
INT = 09
STRICT STRING = hello
STRING = h3llo!
STRING = !!
OTHER =  
OTHER = 

注意:添加八进制等数字时,前导0可能会推断出其他含义,因此,如果打算将'09'视为一个整数(我正在这样做)(例如,expr 09 + 0或使用sed 进行剥离),则最好将它们剥离

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.