ChatGPT解决这个技术问题 Extra ChatGPT

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

我正在尝试做一些足够常见的事情:在 shell 脚本中解析用户输入。如果用户提供了一个有效的整数,脚本会做一件事,如果无效,它会做另一件事。麻烦的是,我还没有找到一种简单(而且相当优雅)的方式来做到这一点——我不想一个接一个地把它分开。

我知道这一定很容易,但我不知道怎么做。我可以用十几种语言做到这一点,但 BASH 不行!

在我的研究中,我发现了这一点:

Regular expression to test whether a string consists of a valid real number in base 10

其中有一个关于正则表达式的答案,但据我所知,这是 C(以及其他)中可用的函数。尽管如此,它看起来还是一个很好的答案,所以我用 grep 尝试了它,但 grep 不知道如何处理它。我尝试了 -P ,它在我的盒子上意味着将其视为 PERL 正则表达式 - 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
仅供参考:[ 与旧版兼容 test[[ 是 Bash 的新东西,操作更多,引用规则不同。如果您已经决定坚持使用 Bash,请选择 [[(它真的更好);如果您需要移植到其他 shell,请完全避免使用 [[

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

^ 表示输入模式的开始

是文字“-”

这 ?表示“前面的 (-) 中的 0 或 1”

+ 表示“前面的 1 个或多个 ([0-9])”

$ 表示输入模式的结束

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

参考:

http://www.tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF


谢谢伊格纳西奥,我马上试试。你介意解释一下,这样我就可以学一点吗?我收集它的内容是,“在字符串 (^) 的开头,减号 (-) 是可选的 (?),后跟 0 到 9 之间的任意数量的字符,包括 0 和 9”......那么 + $ 是什么意思?谢谢。
+ 表示“前面的一个或多个”,$ 表示输入模式的结束。所以正则表达式匹配一个可选的 - 后跟一个或多个十进制数字。
抱怨:ABS 链接
这是一个切线,但请注意,在指定字符范围时,您可能会得到奇怪的结果;例如,[A-z] 不仅会给出 A-Za-z,还会给出 \ []^_`
此外,基于字符排序规则 (see this related question/answer),类似 d[g-i]{2} 的内容最终可能不仅匹配 dig,而且匹配该答案建议的排序规则中的 dish(其中 sh 有向图被视为单个字符,在 h 之后整理)。
O
Olivia Stork

哇...这里有很多好的解决方案!在上述所有解决方案中,我同意@nortally 的观点,即使用-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]+$ ]]

好的!不过与 this 非常相似。
是的。这是相似的。但是,我一直在为“if”语句寻找一个单一的解决方案。我认为我真的不需要为此调用函数。另外,我可以看到函数中的 stderr 重定向到 stdout。当我尝试时,显示了标准错误消息“预期的整数表达式”,这对我来说并不理想。
@PeterHo 在不需要的地方避免使用正则表达式总是一个好主意,因为在大多数情况下正则表达式很昂贵。此解决方案可用作带有 or 子句 test || die invalid 的单行。
您的解决方案和正则表达式之间有一个显着的区别:整数的大小根据 bash 限制进行检查(在我的计算机上是 64 位)。此限制不会影响正则表达式解决方案。因此,您的解决方案将在 64 位计算机上严格大于 9223372036854775807 的数字上失败。
正如我最近发现的那样,有 some caveats
t
tripleee

迟到的人来这里参加聚会。我非常惊讶没有一个答案提到最简单、最快、最便携的解决方案; case 语句。

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

在比较之前修剪任何符号感觉有点像 hack,但这使得 case 语句的表达式变得如此简单。


我希望每次我因为受骗而回到这个问题时都可以投票一次。一个简单但符合 POSIX 标准的解决方案被埋在了底部,这让我大吃一惊。
也许您应该注意空字符串:''|*[!0-9]*)
顺便说一句:这是记录的语法:tldp.org/LDP/abs/html/string-manipulation.html
我并不特别宽恕ABS。这显然也记录在 Bash 手册中。无论如何,您链接到的部分没有描述这个特定的构造,而是例如@Nortally 的答案。
@tripleee 链接文档描述了从案例行中使用的变量中删除字符串前缀的结构。它就在页面底部,但没有锚点,所以我无法直接链接到它,请参阅“子字符串删除”部分
J
JamesThomasMoon

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

我自己的解决方案是使用参数扩展来丢弃所有数字,看看是否还有任何剩余。 (我还在用 3.0,之前没用过 [[expr,但很高兴认识他们。)

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

这可以使用 [ -z "${INPUT_STRING//[0-9]}" ] 进一步改进,但非常好的解决方案!
负面迹象呢?
-eq 解决方案存在一些问题;见这里:stackoverflow.com/a/808740/1858225
空 INPUT_STRING 被视为数字,因此我的情况失败
e
ephemient

为了移植到 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,回显第一个组(或匹配长度,如果没有)并返回成功/失败。这是旧的正则表达式语法,因此多余的 \-\? 表示“可能是 -”,[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]) 表示“零个或多个数字”。


谢谢ehemian,非常感谢。我以前从未见过 =~ 语法 - 仍然不知道它应该意味着什么 - 大约相等?! ...我从来没有对在 BASH 中编程感到兴奋,但有时这是必要的!
awk 中,~ 是“正则表达式匹配”运算符。在 Perl(从 C 复制而来)中,~ 已经用于“位补码”,因此他们使用了 =~。后来的符号被复制到其他几种语言。 (Perl 5.10 和 Perl 6 更喜欢 ~~,但这在这里没有影响。)我想您可以将其视为某种近似相等...
优秀的帖子和编辑!我非常感谢解释它的含义。我希望我可以将您和 Ignacio 的帖子标记为正确答案。 -皱眉-你们俩都很棒。但是由于您的声誉是他的两倍,所以我将其交给伊格纳西奥-希望您理解! -微笑-
B
Benjamin Gruenbaum

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

function is_int() { test "$@" -eq "$@" 2> /dev/null; } 
 
input="-123"
 
if is_int "$input"
then
   echo "Input: ${input}"
   echo "Integer: ${input}"
else
   echo "Not an integer: ${input}"
fi

不必将 $()if 一起使用。这有效:if is_int "$input"。此外,不推荐使用 $[] 形式。请改用 $(())。在两者中,美元符号可以省略:echo "Integer: $((input))" 在脚本中的任何地方都不需要大括号。
我本来希望这也可以将 Bash 的基本符号中的数字作为有效整数处理(当然,根据某些定义,它们是;但它可能与您的不一致)但 test 似乎不支持这一点。 [[ 确实如此。 [[ 16#aa -eq 16#aa ]] && echo integer 打印“整数”。
请注意,[[ 会为此方法返回误报;例如 [[ f -eq f ]] 成功。所以它必须使用 test[
D
Dennis Williamson

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

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      Integer
-44     44      Integer
44-     44      Not integer
4-4     44      Not integer
a4      4       Not integer
4a      4       Not integer
.4      4       Not integer
4.4     44      Not integer
-4.4    44      Not integer
09      9       Not integer

嗨,丹尼斯,感谢您向我介绍上面 match= 右侧的语法。我以前从未注意到过这种类型的语法。我从 tr 中识别出一些语法(我还没有完全掌握的实用程序,但有时会摸索我的方式);我在哪里可以阅读这种语法? (即,这种东西叫什么?)谢谢。
您可以在 Bash 手册页中的“参数扩展”部分中查看有关 ${var//string}${var#string} 的信息,并在“模式匹配”部分中查看 [^[:digit:]]`(也包括在 man 7 regex 中)。
match=${match#0*}去除前导零,它最多去除一个零。使用扩展只能通过 match=${match##+(0)} 使用 extglob 来实现。
9 或 09 不是整数吗?
@MikeQ:如果您认为整数没有前导零,则 09 不是整数。测试是输入 (09) 是否等于经过清理的版本(9 - 一个整数),而它不等于。
T
Trebor Rude

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

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

当然,此解决方案仅在零值对您的应用程序没有意义的情况下才有效。在我的情况下恰好是这样,这比其他解决方案要简单得多。

正如评论中所指出的,这会使您受到代码执行攻击:(( )) 运算符评估 VAR,如 the bash(1) man pageArithmetic Evaluation 部分所述。因此,当 VAR 的内容来源不确定时,不应使用此技术(当然,也不应使用任何其他形式的变量扩展)。


您甚至可以使用 if (( var )); then echo "$var is an int."; fi 变得更简单
但是对于负整数@aaronr,这也将返回true,而不是OP正在寻找的东西。
这很危险,请参阅: n=1 ; var="n" ;如果 (( var ));然后 echo "$var is an int.";菲
这是一个非常糟糕的主意,并且会受到任意代码执行的影响:自己尝试一下:VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi。此时您很高兴我没有输入一些邪恶的命令而不是 ls。因为 OP 提到了用户输入,所以我真的希望您不要在生产代码中将它与用户输入一起使用!
如果字符串包含以下数字,则此方法不起作用:agent007
k
knipwim

或使用 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's answer
谢谢!唯一真正有效的答案!
无声替代:if [[ -n "$(printf "%s" "${2}" | sed s/[0-9]//g)" ]]; then
J
JustinMT

补充 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]...?
M
Mike Q

为了笑,我大致只是快速制定了一组函数来执行此操作(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- 不是等..:

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' 视为 int(我正在这样做)(例如 expr 09 + 0 或使用 sed 剥离),最好将它们剥离