我只是不知道如何确保传递给我的脚本的参数是一个数字。
我想做的就是这样的:
test *isnumber* $1 && VAR=$1 || echo "need a number"
有什么帮助吗?
test && echo "foo" && exit 0 || echo "bar" && exit 1
方法可能会产生一些意想不到的副作用 - 如果回显失败(可能输出到关闭的 FD),exit 0
将被跳过,然后代码将尝试到 echo "bar"
。如果它也失败了,&&
条件将失败,它甚至不会执行 exit 1
!使用实际的 if
语句而不是 &&
/||
不太容易产生意外的副作用。
[[ $1 =~ "^[0-9]+$" ]] && { echo "number"; exit 0; } || { echo "not a number"; exit 1; }
大括号表示不应在子 shell 中执行事情(肯定会使用 ()
括号代替)。警告:永远不要错过最后的分号。否则,您可能会导致 bash
打印出最丑陋(也是最无意义)的错误消息...
[[ 12345 =~ ^[0-9]+$ ]] && echo OKKK || echo NOOO
一种方法是使用正则表达式,如下所示:
re='^[0-9]+$'
if ! [[ $yournumber =~ $re ]] ; then
echo "error: Not a number" >&2; exit 1
fi
如果值不一定是整数,请考虑适当修改正则表达式;例如:
^[0-9]+([.][0-9]+)?$
...或者,处理带符号的数字:
^[+-]?[0-9]+([.][0-9]+)?$
没有 bashisms(即使在 System V sh 中也可以),
case $string in
''|*[!0-9]*) echo bad ;;
*) echo good ;;
esac
这拒绝空字符串和包含非数字的字符串,接受其他所有内容。
负数或浮点数需要一些额外的工作。一个想法是在第一个“坏”模式中排除 -
/ .
并添加更多包含它们的不当使用的“坏”模式(?*-*
/ *.*.*
)
if test ...
无休止的向后/横向兼容性问题要优雅得多
${string#-}
(这在古董 Bourne shell 中不起作用,但在任何 POSIX shell 中都有效)以接受负整数。
'.' | *.*.*
添加到不允许的模式,并将点添加到允许的字符。同样,您可以在前面允许一个可选符号,尽管我更希望 case ${string#[-+]}
简单地忽略该符号。
以下解决方案也可用于基本的 shell,例如 Bourne,而不需要正则表达式。基本上,任何使用非数字的数值计算操作都会导致错误,在 shell 中将被隐式视为错误:
"$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
大多数非 Bash shell 将始终将此表达式评估为 true
Bash 中的行为未记录在案,因此可能会在没有警告的情况下更改
如果值在数字后包含空格(例如“1 a”)会产生错误,例如 bash: [[: 1 a: syntax error in expression (error token is "a")
如果值与 var-name 相同(例如 i="i"),则产生错误,如 bash: [[: i: expression recursion level exceeded (error token is "i")
[[ a -eq a ]]
计算结果为真(两个参数都转换为零)
if ! [ $# -eq 1 -o "$1" -eq "$1" ] 2>/dev/null; then
[
内置函数会将参数评估为算术的 shell 数量不少。在 ksh93 和 mksh 中都是如此。此外,由于这两个都支持数组,因此很容易进行代码注入。请改用模式匹配。
[[ ]]
而不是 [ ]
。也就是说,test
的 POSIX 标准和 bash 自己的文档都未指定此行为; bash 的未来版本可以修改行为以匹配 ksh 而不会破坏任何记录在案的行为承诺,因此不能保证依赖其当前行为持续存在是安全的。
没有人建议 bash 的 extended pattern matching:
[[ $1 == ?(-)+([0-9]) ]] && echo "$1 is an integer"
[[ $1 == ?(-)+([[:digit:]]) ]] && echo "$1 is an integer"
shopt -s extglob
(我赞成,这是我最喜欢的答案之一),因为在 Conditional Constructs 中您可以阅读:当使用 ==
和 !=
运算符时,运算符右侧的字符串被视为一个模式,并根据以下 Pattern Matching 中描述的规则进行匹配,就像启用了 extglob
shell 选项一样。 希望您不要介意!
[[...]]
中的变量不受分词或全局扩展的影响。
[[:digit:]]
而不是 [:digit:]
。
这会测试一个数字是否为非负整数。它独立于 shell(即没有 bashisms)并且仅使用 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";
但这是 INCORRECT,因为它接受任何以数字开头的字符串,如 jilles suggested。
*[!0-9]*
是一种匹配所有至少有 1 个非数字字符的字符串的模式。 ${num##*[!0-9]*}
是“参数扩展”,我们在其中获取 num
变量的内容并删除与模式匹配的最长字符串。如果参数扩展的结果不为空(! [ -z ${...} ]
),那么它是一个数字,因为它不包含任何非数字字符。
(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 Rank
isuint_Case 8,080µs 6,848µs 14,928µs 100.00%
isuint_Parm 10,571µs 13,061µs 23,632µs 158.31%
isuint_Regx 12,865µs 15,407µs 28,272µs 189.39%
isuint_Bash 19,054µs 17,182µs 36,236µs 242.74%
isuint_Grep 1,333,786µs 1,416,626µs 2,750,412µs 18424.52%
Function Number NaN Total Rank
isint_Case 8,860µs 7,813µs 16,673µs 100.00%
isint_Parm 14,141µs 16,774µs 30,915µs 185.42%
isint_Regx 14,202µs 17,375µs 31,577µs 189.39%
isint_Bash 18,988µs 16,598µs 35,586µs 213.43%
Function Number NaN Total Rank
isnum_Case 8,935µs 9,232µs 18,167µs 100.00%
isnum_Parm 18,898µs 22,577µs 41,475µs 228.30%
isnum_Regx 25,336µs 42,825µs 68,161µs 375.19%
您可以下载 full isnum comparission script here 或 full isnum comparission script as text here.,(使用 UTF8 和 LATIN 处理)。
结论
案例方式显然是最快的!比正则表达式快 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 ;}
关于兼容性
为此,我编写了一个 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
据我所知,regex 和 bash 的整数 等其他基于 bash 的解决方案将不起作用在许多其他 shell 中,forks 资源昂贵,我更喜欢 case
方式(就在 参数扩展之前 也大多兼容)。
case
答案?那个得到了我的投票,既简单又优雅。在我的测试中,它比您的两种选择都快得多。在 IdeOne 上,它不太明显,但速度更快:ideone.com/AVvMOU
+Numberr
列,我不会尝试在那里解释这一点;)
is_float() { is_num "${1/[eE][-+]/}"; }
我对直接在 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(){ printf '%f' "$1" &>/dev/null && echo "this is a number" || echo "not a number"; }
isnumber 23 && echo "this is a number" || echo "not a number"
2>/dev/null
,这样isnumber "foo"
就不会污染stderr吗?
isnumber "'a"
将返回 true。这在 POSIX spec 中有记录,您将在其中阅读:如果前导字符是单引号或双引号,则该值应是单引号后面字符的基础代码集中的数值或双引号。
我正在查看答案并且......意识到没有人考虑过浮点数(带点)!
使用 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
要匹配 JUST 整数,请使用以下命令:
# 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
等。
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
man -P 'less +/parameter\/' bash
部分。每天学习新东西。 :)
\-
来解决该问题。使用 [0-9\-\.\+]
计算浮点数和有符号数。
echo $i | python -c $'import sys\ntry:\n float(sys.stdin.read().rstrip())\nexcept:\n sys.exit(1)' && echo yes || echo no
对于我的问题,我只需要确保用户不会意外输入一些文本,因此我尽量保持简单易读
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' is a number"
这可以通过使用 grep
查看所讨论的变量是否与扩展的正则表达式匹配来实现。
测试整数 1120:
yournumber=1120
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
echo "Valid number."
else
echo "Error: not a number."
fi
输出:Valid number.
测试非整数 1120a:
yournumber=1120a
if echo "$yournumber" | grep -qE '^[0-9]+$'; then
echo "Valid number."
else
echo "Error: not a number."
fi
输出:Error: not a number.
解释
grep,-E 开关允许我们使用扩展正则表达式'^[0-9]+$'。这个正则表达式意味着变量应该只包含从 ^ 开始到 $ 结尾的数字 0-9 零到九,并且应该至少包含 + 一个字符。
grep,-q quiet 开关会关闭任何输出,无论它是否找到任何东西。
if 检查 grep 的退出状态。退出状态 0 表示成功,任何更大的都表示错误。如果 grep 命令找到匹配项,则退出状态为 0,如果没有,则退出状态为 1;
因此,将它们放在一起,在 if
测试中,我们 echo
变量 $yournumber
和 |
通过管道将它与 -q
开关匹配 -E
扩展正则表达式 {8 } 表达。如果 grep
成功找到匹配项,则 grep
的退出状态将为 0
,否则为 1
。如果匹配成功,我们echo "Valid number."
。如果不匹配,我们echo "Error: not a number."
。
对于花车或双打
对于浮点数或双精度数,我们只需将正则表达式从 '^[0-9]+$'
更改为 '^[0-9]*\.?[0-9]+$'
。
测试浮子 1120.01:
yournumber=1120.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
echo "Valid number."
else
echo "Error: not a number."
fi
输出:Valid number.
测试浮子 11.20.01:
yournumber=11.20.01
if echo "$yournumber" | grep -qE '^[0-9]*\.?[0-9]+$'; then
echo "Valid number."
else
echo "Error: not a number."
fi
输出:Error: not a number.
对于负数
要允许负整数,只需将正则表达式从 '^[0-9]+$'
更改为 '^\-?[0-9]+$'
。
要允许负浮点数或双精度数,只需将正则表达式从 '^[0-9]*\.?[0-9]+$'
更改为 '^\-?[0-9]*\.?[0-9]+$'
。
[-]
而不是 \-
和 [.]
而不是 \.
有点冗长,但它意味着如果在使用反斜杠的上下文中使用字符串,则不必更改它们)。
if [[ $yournumber =~ ^[0-9]+([.][0-9]+)?$ ]] ; then
使用了不同的方法,但不知何故,它在升级到 Ubuntu 20.04 后停止工作,您的第一个“测试整数”解决方案在 20.04 中也是如此。我不能说它是否与升级有关,或者我的脚本在第一个实例中是错误的,并且 - 不知何故 - 但在旧系统中工作。非常感谢。
#!/bin/sh
?如果是这样,只要您使用 #!/bin/bash
shebang,它应该仍然可以在现代 Ubuntu 中工作,并避免使用 sh scriptname
启动脚本(这会忽略 shebang 并强制使用 sh
而不是 bash
)。
老问题,但我只是想解决我的解决方案。这个不需要任何奇怪的 shell 技巧,或者依赖于一些从未存在过的东西。
if [ -n "$(printf '%s\n' "$var" | sed 's/[0-9]//g')" ]; then
echo 'is not numeric'
else
echo 'is numeric'
fi
基本上它只是从输入中删除所有数字,如果你留下一个非零长度的字符串,那么它不是一个数字。
var
,这将失败。
$'0\n\n\n1\n\n\n2\n\n\n3\n'
的变量。
我会试试这个:
printf "%g" "$var" &> /dev/null
if [[ $? == 0 ]] ; then
echo "$var is a number."
else
echo "$var is not a number."
fi
注意:这将 nan 和 inf 识别为数字。
%f
可能更好)
if
本身中呢?这就是 if
所做的... if printf "%g" "$var" &> /dev/null; then ...
'a
这样的字符串。
还不能发表评论,所以我将添加我自己的答案,这是对 glenn jackman 使用 bash 模式匹配的答案的扩展。
我最初的需要是识别数字并区分整数和浮点数。函数定义扣除为:
function isInteger() {
[[ ${1} == ?(-)+([0-9]) ]]
}
function isFloat() {
[[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}
我使用单元测试(使用 shUnit2)来验证我的模式是否按预期工作:
oneTimeSetUp() {
int_values="0 123 -0 -123"
float_values="0.0 0. .0 -0.0 -0. -.0 \
123.456 123. .456 -123.456 -123. -.456
123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
}
testIsIntegerIsFloat() {
local value
for value in ${int_values}
do
assertTrue "${value} should be tested as integer" "isInteger ${value}"
assertFalse "${value} should not be tested as float" "isFloat ${value}"
done
for value in ${float_values}
do
assertTrue "${value} should be tested as float" "isFloat ${value}"
assertFalse "${value} should not be tested as integer" "isInteger ${value}"
done
}
注意:可以修改 isFloat 模式,使其对小数点 (@(.,)
) 和 E 符号 (@(Ee)
) 更加宽容。我的单元测试仅测试整数或浮点值,但不测试任何无效输入。
[[ $1 =~ ^-?[0-9]+$ ]] && echo "number"
不要忘记 -
包括负数!
=~
至少可以追溯到 bash 3.0。
@charles Dufy 和其他人已经给出了明确的答案。纯 bash 解决方案将使用以下内容:
string="-12,345"
if [[ "$string" =~ ^-?[0-9]+[.,]?[0-9]*$ ]]
then
echo $string is a number
else
echo $string is not a number
fi
尽管对于实数,在 radix point 之前不一定要有数字。
为了提供对浮点数和科学记数法的更彻底的支持(C/Fortran 中的许多程序,否则将以这种方式导出浮点数),对这一行的有用补充如下:
string="1.2345E-67"
if [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]?-?[0-9]+$ ]]
then
echo $string is a number
else
echo $string is not a number
fi
因此,如果您正在寻找任何特定类型,则会导致一种区分数字类型的方法:
string="-12,345"
if [[ "$string" =~ ^-?[0-9]+$ ]]
then
echo $string is an integer
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*$ ]]
then
echo $string is a float
elif [[ "$string" =~ ^-?[0-9]*[.,]?[0-9]*[eE]-?[0-9]+$ ]]
then
echo $string is a scientific number
else
echo $string is not a number
fi
注意:我们可以列出十进制和科学记数法的语法要求,其中之一是允许逗号作为小数点,以及“.”。然后我们会断言必须只有一个这样的小数点。 [Ee] 浮点数中可以有两个 +/- 符号。我从 Aulu 的工作中学到了更多规则,并针对诸如 '' '-' '-E-1' '0-0' 之类的错误字符串进行了测试。这是我的 regex/substring/expr 工具,它们似乎一直在坚持:
parse_num() {
local r=`expr "$1" : '.*\([.,]\)' 2>/dev/null | tr -d '\n'`
nat='^[+-]?[0-9]+[.,]?$' \
dot="${1%[.,]*}${r}${1##*[.,]}" \
float='^[\+\-]?([.,0-9]+[Ee]?[-+]?|)[0-9]+$'
[[ "$1" == $dot ]] && [[ "$1" =~ $float ]] || [[ "$1" =~ $nat ]]
} # usage: parse_num -123.456
我使用 expr。如果您尝试将零添加到非数字值,它将返回非零:
if expr -- "$number" + 0 > /dev/null 2>&1
then
echo "$number is a number"
else
echo "$number isn't a number"
fi
如果您需要非整数,则可以使用 bc,但我不认为 bc
具有完全相同的行为。将零添加到非数字会使您为零,并且它也返回零值。也许您可以将 bc
和 expr
结合起来。使用 bc
将零添加到 $number
。如果答案是 0
,则尝试 expr
来验证 $number
不为零。
expr -- "$number" + 0
;然而这仍然会假装0 isn't a number
。从 man expr
:Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null or 0,
expr
。如果您仅限于像 POSIX sh
这样的较小的 Bourne shell,那么也许可以。
$(( ))
。您说的是 1970 年代的伯恩需要 expr
。
一种简单的方法是检查它是否包含非数字字符。您用任何内容替换所有数字字符并检查长度。如果有长度,则不是数字。
if [[ ! -n ${input//[0-9]/} ]]; then
echo "Input Is A Number"
fi
[[ ! -n ${1//[+\-0-9]/} ]] && echo "is a number" || echo "is not a number";
。现在的问题是 +-123
也会通过。
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_03.html
您还可以使用 bash 的字符类。
if [[ $VAR = *[[:digit:]]* ]]; then
echo "$VAR is numeric"
else
echo "$VAR is not numeric"
fi
数字将包括空格、小数点和表示浮点的“e”或“E”。
但是,如果您指定 C 风格的十六进制数,即“0xffff”或“0XFFFF”,[[:digit:]] 将返回 true。这里有点陷阱,bash 允许您执行“0xAZ00”之类的操作,并且仍然将其计为一个数字(这不是来自 GCC 编译器的一些奇怪的怪癖,它允许您对 16 以外的基数使用 0x 表示法吗? )
如果您的输入完全不受信任,您可能需要在测试它是否为数字之前测试“0x”或“0X”,除非您想接受十六进制数字。这将通过以下方式实现:
if [[ ${VARIABLE:1:2} = "0x" ]] || [[ ${VARIABLE:1:2} = "0X" ]]; then echo "$VAR is not numeric"; fi
[[ $VAR = *[[:digit:]]* ]]
将返回 true,而不是如果它是一个整数。
[[ "z3*&" = *[[:digit:]]* ]] && echo "numeric"
打印 numeric
。在 bash 版本 3.2.25(1)-release
中测试。
[[ -n $VAR && $VAR != *[^[:digit:]]* ]]
因为我最近不得不篡改这个并且最喜欢karttu的单元测试方法。我修改了代码并添加了一些其他解决方案,自己尝试一下看看结果:
#!/bin/bash
# N={0,1,2,3,...} by syntaxerror
function isNaturalNumber()
{
[[ ${1} =~ ^[0-9]+$ ]]
}
# Z={...,-2,-1,0,1,2,...} by karttu
function isInteger()
{
[[ ${1} == ?(-)+([0-9]) ]]
}
# Q={...,-½,-¼,0.0,¼,½,...} by karttu
function isFloat()
{
[[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]
}
# R={...,-1,-½,-¼,0.E+n,¼,½,1,...}
function isNumber()
{
isNaturalNumber $1 || isInteger $1 || isFloat $1
}
bools=("TRUE" "FALSE")
int_values="0 123 -0 -123"
float_values="0.0 0. .0 -0.0 -0. -.0 \
123.456 123. .456 -123.456 -123. -.456 \
123.456E08 123.E08 .456E08 -123.456E08 -123.E08 -.456E08 \
123.456E+08 123.E+08 .456E+08 -123.456E+08 -123.E+08 -.456E+08 \
123.456E-08 123.E-08 .456E-08 -123.456E-08 -123.E-08 -.456E-08"
false_values="blah meh mooh blah5 67mooh a123bc"
for value in ${int_values} ${float_values} ${false_values}
do
printf " %5s=%-30s" $(isNaturalNumber $value) ${bools[$?]} $(printf "isNaturalNumber(%s)" $value)
printf "%5s=%-24s" $(isInteger $value) ${bools[$?]} $(printf "isInteger(%s)" $value)
printf "%5s=%-24s" $(isFloat $value) ${bools[$?]} $(printf "isFloat(%s)" $value)
printf "%5s=%-24s\n" $(isNumber $value) ${bools[$?]} $(printf "isNumber(%s)" $value)
done
所以 isNumber() 包括破折号、逗号和指数符号,因此在整数和浮点数上返回 TRUE,而另一方面 isFloat() 在整数值上返回 FALSE,isInteger() 同样在浮点数上返回 FALSE。为了您的方便,全部作为一个衬垫:
isNaturalNumber() { [[ ${1} =~ ^[0-9]+$ ]]; }
isInteger() { [[ ${1} == ?(-)+([0-9]) ]]; }
isFloat() { [[ ${1} == ?(-)@(+([0-9]).*([0-9])|*([0-9]).+([0-9]))?(E?(-|+)+([0-9])) ]]; }
isNumber() { isNaturalNumber $1 || isInteger $1 || isFloat $1; }
function
关键字,因为它没有任何用处。另外,我不确定返回值的用处。除非另有说明,否则这些函数将返回最后一个命令的退出状态,因此您不需要自己进行 return
任何操作。
return
令人困惑,使其可读性降低。是否使用 function
关键字更多是个人风格的问题,至少我将它们从一个衬里中删除以节省一些空间。谢谢。
我使用 printf 作为提到的其他答案,如果您提供格式字符串“%f”或“%i”,printf 将为您进行检查。比重新发明检查更容易,语法简单而简短,并且 printf 无处不在。所以在我看来这是一个不错的选择——你也可以使用下面的想法来检查一系列的东西,它不仅对检查数字有用。
declare -r CHECK_FLOAT="%f"
declare -r CHECK_INTEGER="%i"
## <arg 1> Number - Number to check
## <arg 2> String - Number type to check
## <arg 3> String - Error message
function check_number() {
local NUMBER="${1}"
local NUMBER_TYPE="${2}"
local ERROR_MESG="${3}"
local -i PASS=1
local -i FAIL=0
case "${NUMBER_TYPE}" in
"${CHECK_FLOAT}")
if ((! $(printf "${CHECK_FLOAT}" "${NUMBER}" &>/dev/random;echo $?))); then
echo "${PASS}"
else
echo "${ERROR_MESG}" 1>&2
echo "${FAIL}"
fi
;;
"${CHECK_INTEGER}")
if ((! $(printf "${CHECK_INTEGER}" "${NUMBER}" &>/dev/random;echo $?))); then
echo "${PASS}"
else
echo "${ERROR_MESG}" 1>&2
echo "${FAIL}"
fi
;;
*)
echo "Invalid number type format: ${NUMBER_TYPE} to check_number()." 1>&2
echo "${FAIL}"
;;
esac
}
>$ var=45
>$ (($(check_number $var "${CHECK_INTEGER}" "Error: Found $var - An integer is required."))) && { echo "$var+5" | bc; }
我喜欢 Alberto Zaccagni 的回答。
if [ "$var" -eq "$var" ] 2>/dev/null; then
重要的先决条件: - 没有产生子 shell - 没有调用 RE 解析器 - 大多数 shell 应用程序不使用实数
但是如果 $var
很复杂(例如关联数组访问),并且数字是非负整数(大多数用例),那么这可能更有效吗?
if [ "$var" -ge 0 ] 2> /dev/null; then ..
要捕获负数:
if [[ $1 == ?(-)+([0-9.]) ]]
then
echo number
else
echo not a number
fi
When the ‘==’ and ‘!=’ operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below in Pattern Matching, as if the extglob shell option were enabled.
gnu.org/software/bash/manual/bashref.html#index-_005b_005b 时会自动激活 @tripleee 扩展通配符
你也可以像这样使用“let”:
[ ~]$ var=1
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=01
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s a number
[ ~]$ var=toto
[ ~]$ let $var && echo "It's a number" || echo "It's not a number"
It\'s not a number
[ ~]$
但我更喜欢使用 "=~" Bash 3+ 运算符,就像这个线程中的一些答案一样。
几乎就像你想要的语法一样。只需要一个函数 isnumber
:
#!/usr/bin/bash
isnumber(){
num=$1
if [ -z "${num##*[!0-9]*}" ];
then return 1
else
return 0
fi
}
$(isnumber $1) && VAR=$1 || echo "need a number";
echo "VAR is $VAR"
测试:
$ ./isnumtest 10
VAR is 10
$ ./isnumtest abc10
need a number
VAR is
printf '%b' "-123\nABC" | tr '[:space:]' '_' | grep -q '^-\?[[:digit:]]\+$' && echo "Integer." || echo "NOT integer."
如果您不接受负整数,请删除 grep 匹配模式中的 -\?
。
在这里用一个正则表达式做同样的事情,测试整个部分和小数部分,用点分隔。
re="^[0-9]*[.]{0,1}[0-9]*$"
if [[ $1 =~ $re ]]
then
echo "is numeric"
else
echo "Naahh, not numeric"
fi
.
a=''
和仅包含句点 a='.'
的字符串,因此您的代码有点损坏...
我使用以下(对于整数):
## ##### constants
##
## __TRUE - true (0)
## __FALSE - false (1)
##
typeset -r __TRUE=0
typeset -r __FALSE=1
## --------------------------------------
## isNumber
## check if a value is an integer
## usage: isNumber testValue
## returns: ${__TRUE} - testValue is a number else not
##
function isNumber {
typeset TESTVAR="$(echo "$1" | sed 's/[0-9]*//g' )"
[ "${TESTVAR}"x = ""x ] && return ${__TRUE} || return ${__FALSE}
}
isNumber $1
if [ $? -eq ${__TRUE} ] ; then
print "is a number"
fi
-n
等(因为 echo
),并且您接受带有尾随换行符的变量(因为 $(...)
)。顺便说一句,print
不是有效的 shell 命令。
我尝试了 Ultrasawblade 的配方,因为它对我来说似乎最实用,但无法让它发挥作用。最后,我设计了另一种方法,与其他参数替换一样,这次使用正则表达式替换:
[[ "${var//*([[:digit:]])}" ]]; && echo "$var is not numeric" || echo "$var is numeric"
它删除了 $var 中的每个 :digit: 类字符,并检查我们是否留下了一个空字符串,这意味着原始字符串只有数字。
我喜欢这个的是它的小尺寸和灵活性。在这种形式下,它仅适用于非定界的、以 10 为基数的整数,但您当然可以使用模式匹配来满足其他需求。
sed
是 POSIX,而您的解决方案是 bash
。两者都有其用途
不定期副业成功案例分享
^-?
而不是^-*
。[[ $yournumber =~ ^[0-9]+$ ]]
。=~
右侧文字正则表达式中的反斜杠处理在 3.1 和 3.2 之间发生了变化,而赋值中的反斜杠处理在所有相关的 bash 版本中都是不变的。因此,遵循在使用=~
匹配变量之前始终将正则表达式分配给变量的做法可以避免意外。我这样做是为了教导良好的习惯,即使这个特殊的正则表达式没有反斜杠转义。