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

我只想这样做:

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

有什么帮助吗?


当前回答

接受的答案在所有情况下都不适用于我,BASH 4+因此:

# -- is var an integer? --
# trim leading/trailing whitespace, then check for digits return 0 or 1
# Globals: None
# Arguments: string
# Returns: boolean
# --
is_int() {
    str="$(echo -e "${1}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
    case ${str} in ''|*[!0-9]*) return 1 ;; esac
    return 0
}

如何使用它?

有效(将返回0=true):

is_int "100" && echo "return 0" || echo "return 1"

无效(将返回1=false):

is_int "100abc" && echo "returned 0" || echo "returned 1"
is_int ""  && echo "returned 0" || echo "returned 1"
is_int "100 100"  && echo "returned 0" || echo "returned 1"
is_int "      "  && echo "returned 0" || echo "returned 1"
is_int $NOT_SET_VAR  && echo "returned 0" || echo "returned 1"
is_int "3.14"   && echo "returned 0" || echo "returned 1"

输出:

returned 0
returned 1
returned 1
returned 1
returned 1
returned 1
returned 1

注意,在Bash中,1=假,0=真。我只是把它打印出来,而更可能是这样的:

if is_int ${total} ; then
    # perform some action 
fi

其他回答

我会这样做:

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

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

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

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

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

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

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

…或,用符号处理数字:

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

一些性能和兼容性提示

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

我回顾了最相关的方法并进行了比较。

无符号整数is_uint()

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

使用参数展开(这是我之前的做法!)isuint_Parm(){[“$1”]&&[-z“${1//[0-9]}”];}使用fork进行grepisuint_Grep(){Grep-qE‘^[0-9]+$‘<<“$1”;}我只测试了这个方法一次,因为它非常慢。这只是为了说明不该做什么。使用bash整数功能isuint_Bash(){((10#$1>=0))2>/dev/null;}或更好:isuint_Bash(){set--${1//[+-]/.};((10#$1>=0))2>/dev/null;}用例isuint_Case(){'|*[!0-9]*中的Case$1)return 1;;esac;}使用bash的正则表达式isuint_Regx(){[[$1=~^[0-9]+$]];}

有符号整数is_int()

这些函数实现代码以评估表达式是否为带符号整数,即,如上所述,但允许在数字之前使用可选符号。

使用参数展开isint_Par(){local chk=${1#[+-]};[“$chk”]&&[-z“${chk//[0-9]}”];}使用bash整数功能isint_Bash(){set--“${1//[!+-]}”${1#${1//[!+-]}};(( ( 0 ${1:-+} 10#$2 ) ? 1:1))2>/dev/null;}用例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]])$]];}用例“”|中的isnum_Case(){Case${1#[-+]}|*[!0-9.]*|*.*.*)返回1;;esac;}

概念测试

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

testcases=(
    0  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' Value\\Func \
    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+='   ##'
        else
            outstr+='   --'
        fi
    done
    printf '%-11s %s\n' "$var" "$outstr"
done

应输出:

Value\Func   UPrm UGrp UBsh UCse URgx IPrm IBsh ICse IRgx NPrm NCse NRgx
0              ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##
1              ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##
42             ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##   ##
-3             --   --   --   --   --   ##   ##   ##   ##   ##   ##   ##
+42            --   --   --   --   --   ##   ##   ##   ##   ##   ##   ##
+3.            --   --   --   --   --   --   --   --   --   ##   ##   ##
.9             --   --   --   --   --   --   --   --   --   ##   ##   ##
3.14           --   --   --   --   --   --   --   --   --   ##   ##   ##
+3.141         --   --   --   --   --   --   --   --   --   ##   ##   ##
-31.4          --   --   --   --   --   --   --   --   --   ##   ##   ##
               --   --   --   --   --   --   --   --   --   --   --   --
.              --   --   --   --   --   --   --   --   --   --   --   --
3-3            --   --   --   --   --   --   ##   --   --   --   --   --
3.1.4          --   --   --   --   --   --   --   --   --   --   --   --
3a             --   --   --   --   --   --   --   --   --   --   --   --
a3             --   --   --   --   --   --   --   --   --   --   --   --
blah           --   --   --   --   --   --   --   --   --   --   --   --
Good day!      --   --   --   --   --   --   --   --   --   --   --   --

我希望!(注意: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       6499µs      6566µs         13065µs    100.00%
isuint_Parm      26687µs     31600µs         58287µs    446.13%
isuint_Regx      36511µs     40181µs         76692µs    587.00%
isuint_Bash      43819µs     40311µs         84130µs    643.93%
isuint_Grep    1298265µs   1224112µs       2522377µs  19306.37%

Function          Number         NaN          Total
isint_Case       22687µs     21914µs         44601µs    100.00%
isint_Parm       35765µs     34428µs         70193µs    157.38%
isint_Regx       36949µs     42319µs         79268µs    177.73%
isint_Bash       55368µs     65095µs        120463µs    270.09%

Function          Number         NaN          Total
isnum_Case       23313µs     23446µs         46759µs    100.00%
isnum_Parm       35677µs     42169µs         77846µs    166.48%
isnum_Regx       51864µs     69502µs        121366µs    259.56%

您可以在此处下载完整的isnum比较脚本,也可以在此处以文本形式下载完整的is num比较脚本。,(带UTF8和LATIN处理)。

结论

案件处理方式显然是最快的!大约比正则表达式快3倍,比使用参数扩展快2倍。在不需要时,应避免fork(到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 ;}

关于兼容性

为此,我根据之前的测试编写了一个小测试脚本,包括:

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

我知道其他基于bash的解决方案(如regex和bash的整数)在许多其他shell中都不起作用,并且fork资源昂贵,我更喜欢这种情况(就在参数扩展之前,这也是最兼容的)。

快速和肮脏:我知道这不是最优雅的方式,但我通常只是在上面加一个零并测试结果。像这样:

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

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框架中最健壮的。。。