(第 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
isuint_Case 6762µs 8492µs 15254µs 100.00%
isuint_Bash 13478µs 12739µs 26217µs 171.87%
isuint_Parm 11324µs 18807µs 30131µs 197.53%
isuint_Regx 20777µs 27616µs 48393µs 317.25%
isuint_Grep 1516390µs 1491751µs 3008141µs 19720.34%
Function Number NaN Total
isint_Case 8630µs 8042µs 16672µs 100.00%
isint_Bash 14254µs 12272µs 26526µs 159.10%
isint_Parm 16445µs 20491µs 36936µs 221.54%
isint_Regx 23661µs 28287µs 51948µs 311.59%
Function Number NaN Total
isnum_Case 9579µs 10328µs 19907µs 100.00%
isnum_Parm 21115µs 28983µs 50098µs 251.66%
isnum_Regx 35552µs 58453µs 94005µs 472.22%
结论
-
case 方式显然是最快的!比 regex 快大约 3 倍,比使用 参数扩展 快 2 倍。
-
forks(到
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
据我所知,其他基于 bash 的解决方案如 regex 和 bash's integer 赢了不能在许多其他 shell 中工作,并且 forks 资源昂贵,我更喜欢 case 方式
(就在 参数扩展 之前,这也是大部分兼容的)。