我无法弄清楚如何确保传递给我的脚本的参数是数字还是其他类型。
我想要做的只是像这样:
test *isnumber* $1 && VAR=$1 || echo "need 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]+)?$
[[ $yournumber =~ ^[0-9]+$ ]]
。 - konsolebox=~
右侧的字面正则表达式中反斜杠处理在3.1和3.2之间有所改变,而在赋值中反斜杠处理在所有相关版本的bash中都是不变的。因此,始终将正则表达式分配给变量,然后使用=~
进行匹配可以避免意外。即使这个特定的正则表达式没有反斜杠转义,我也会这样做来教导好的习惯。 - Charles Duffyif [[ 123 =~ '^[0-9]+$' ]]; then echo good; fi
,但没有输出结果。但是这个命令:re='^[0-9]+$'; if [[ 123 =~ $re ]]; then echo good; fi
输出了 good
。为什么?我需要在第一个版本中转义一些内容吗? - Frozen Flame不使用 bashisms(即使在 System V sh 中也可以工作),
case $string in
''|*[!0-9]*) echo bad ;;
*) echo good ;;
esac
拒绝空字符串和包含非数字的字符串,接受其他所有内容。
对于负数或浮点数需要一些额外的处理。一个想法是在第一个“不好”的模式中排除-
/.
并添加更多的“不好”的模式,其中包含它们的不适当使用(?*-*
/*.*.*
)。
${string#-}
(这在旧版Bourne shell中不起作用,但在任何POSIX shell中都起作用),以接受负整数。 - Gilles 'SO- stop being evil''.'|*.*.*
添加到不允许的模式中,并将点号添加到允许的字符中。同样地,您可以在前面允许一个可选的符号,尽管这时我更喜欢使用case ${string#[-+]}
来忽略符号。 - tripleee以下解决方案也适用于基本的Shell,例如Bourne,无需使用正则表达式。 基本上,任何使用非数字进行数值评估操作都将导致错误,这将被隐式地视为Shell中的false:
"$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
true
bash:[[: 1 a:表达式中的语法错误(错误令牌为“a”)
bash:[[: i:超过表达式递归级别(错误令牌为“i”)
[[ a -eq a ]]
将被评估为 true(两个参数都被转换为零)。 - Tgrif ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
- haridsv[
内置函数中的参数作为算术表达式进行求值,包括ksh93和mksh。此外,由于这两个shell都支持数组,存在易受代码注入攻击的风险。请改用模式匹配。 - ormaaj[[ ]]
中按数字环境规则解释,而不适用于[ ]
。也就是说,这种行为未被POSIX标准或bash自己的文档所指定;未来的bash版本可能会修改行为以匹配ksh,而不会违反任何记录的行为承诺,因此依赖其当前的行为并不保证是安全的。 - Charles Duffyshopt -s extglob
(我点赞了它,这是我在这里最喜欢的答案之一),因为在条件结构中,你可以读到:当使用 ==
和 !=
运算符时,运算符右侧的字符串被视为模式,并根据下面在模式匹配中描述的规则进行匹配,就好像启用了 extglob
shell 选项。 希望你不介意! - gniourf_gniourf[[...]]
中的变量不受单词拆分或通配符扩展的影响。 - glenn jackman[[:digit:]]
代替[:digit:]
来匹配POSIX。 - bugi不同类型的测试有一些强烈不同的方法。
我回顾了大多数相关的方法并建立了这个比较。
is_uint()
这些函数实现了用于评估一个表达式是否为无符号整数(即完全由数字组成)的代码。
Using parameter expansion
(This was my approach before all this!)
isuint_Parm() { [ "$1" ] && [ -z "${1//[0-9]}" ] ;}
Using fork to grep
isuint_Grep() { grep -qE '^[0-9]+$' <<<"$1"; }
I test this method only once because it's very slow. This is just there to show what not to do.
Using bash integer capabilities
isuint_Bash() { (( 10#$1 >= 0 )) 2>/dev/null ;}
or better:
isuint_Bash() { set -- ${1//[+-]/.};(( 10#$1 >= 0 )) 2>/dev/null ;}
Using case
isuint_Case() { case $1 in ''|*[!0-9]*) return 1;;esac;}
Using bash's regex
isuint_Regx() { [[ $1 =~ ^[0-9]+$ ]] ;}
is_int()
这些函数实现了代码来判断一个表达式是否为有符号整数,即允许数字前有可选的符号。
Using parameter expansion
isint_Parm() { local chk=${1#[+-]}; [ "$chk" ] && [ -z "${chk//[0-9]}" ] ;}
Using bash integer capabilities
isint_Bash() { set -- "${1//[!+-]}" ${1#${1//[!+-]}};
(( ( 0 ${1:-+} 10#$2 ) ? 1:1 )) 2>/dev/null ;}
Using case
isint_Case() { case ${1#[-+]} in ''|*[!0-9]*) return 1;;esac;}
Using bash's regex
isint_Regx() { [[ $1 =~ ^[+-]?[0-9]+$ ]] ;}
is_num()
这些函数实现了用于判断表达式是否为浮点数的代码,即允许可选的小数点和小数点后的其他数字。但是,它不尝试覆盖科学计数法表示的数字表达式(例如1.0234E-12)。
Using parameter expansion
isnum_Parm() { local ck=${1#[+-]};ck=${ck/.};[ "$ck" ]&&[ -z "${ck//[0-9]}" ];}
Using bash's regex
isnum_Regx() { [[ $1 =~ ^[+-]?([0-9]+([.][0-9]*)?|\.[0-9]+)$ ]] ;}
Using case
isnum_Case() { case ${1#[-+]} in ''|.|*[!0-9.]*|*.*.*) return 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%
case
方法显然是最快的!大约比regex
快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 ;}
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
case
方法(就在参数扩展之前,这种方法也大多数兼容)。isnum(){ case ${1#[-+]} in ''|*[!0-9.]*|*.*.*) return -1;;esac ;}
...看起来不错! - F. Hauri - Give Up GitHubNaN
测试中,isuint_Bash
比case
稍微快一些。如果你处于一个每个周期都很重要的紧密循环中,并且你预计大部分输入都是无效的,那么这可能会使参数扩展版本更有优势。但仅适用于uint
情况。也许差异很小,已经在测量误差范围内了。 - tripleeeis_float() { is_num "${1/[eE][-+]/}"; }
- kvantour这个脚本测试一个数字是否为非负整数。它与 shell 无关(即没有 bashism)并且只使用了 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";
但这是不正确的,因为它接受任何以数字开头的字符串,正如jilles建议的。
*[!0-9]*
是一个匹配至少有1个非数字字符的所有字符串的模式。
${num##*[!0-9]*}
是“参数扩展”,它获取 num
变量的内容并删除与该模式匹配的最长字符串。
如果参数扩展的结果不为空 (! [ -z ${...} ]
),则它是一个数字,因为它不包含任何非数字字符。 - mrucci122s
:-(
。 - Hastur我对在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 23 && echo "this is a number" || echo "not a number"
。请注意,这是一句建议,而非解释。 - michael2>/dev/null
吗?这样一来,isnumber "foo"
就不会污染标准错误输出了。 - gioeleisnumber "'a"
将返回true。在POSIX规范中有记录,你可以看到:如果前导字符是单引号或双引号,则该值应为后面紧随单引号或双引号的字符在底层代码集中的数字值。 - gniourf_gniourf我在看回答时发现没人提到带点的浮点数!
使用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
要仅匹配整数,请使用以下内容:
# change check line to:
check=`echo "$1" | grep -E ^\-?[0-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
等一样。 - tripleee对于我的问题,我只需要确保用户不会意外输入文本,因此我尝试保持简单和易读
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' 是一个数字"
- F. Hauri - Give Up GitHub
test && echo "foo" && exit 0 || echo "bar" && exit 1
方法可能会产生一些意想不到的副作用 - 如果 Echo 失败了(也许输出到了一个关闭的FD),exit 0
将被跳过,然后代码将尝试echo "bar"
。如果这也失败了,&&
条件将失败,甚至不会执行exit 1
! 使用实际的if
语句而不是&&
/||
更不容易出现意外的副作用。 - Charles Duffy[[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }
大括号表示不应该在子shell中执行事情(如果使用圆括号()
就会明确这种情况)。注意:永远不要漏掉最后的分号。否则,你可能会导致bash打印出最丑陋(并且毫无意义)的错误消息... - syntaxerror[[ 12345 =~ ^ [0-9] + $]] && echo OKKK || echo NOOO
。 - Treviño