我正在尝试做一些足够常见的事情:在 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,请完全避免使用 [[
。
[[ $var =~ ^-?[0-9]+$ ]]
^ 表示输入模式的开始
是文字“-”
这 ?表示“前面的 (-) 中的 0 或 1”
+ 表示“前面的 1 个或多个 ([0-9])”
$ 表示输入模式的结束
所以正则表达式匹配一个可选的 -
(对于负数的情况),后跟一个或多个十进制数字。
参考:
http://www.tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF
哇...这里有很多好的解决方案!在上述所有解决方案中,我同意@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]+$ ]]
test || die invalid
的单行。
迟到的人来这里参加聚会。我非常惊讶没有一个答案提到最简单、最快、最便携的解决方案; case
语句。
case ${variable#[-+]} in
*[!0-9]* | '') echo Not a number ;;
* ) echo Valid number ;;
esac
在比较之前修剪任何符号感觉有点像 hack,但这使得 case 语句的表达式变得如此简单。
''|*[!0-9]*)
我喜欢使用 -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
为了移植到 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])
表示“零个或多个数字”。
awk
中,~
是“正则表达式匹配”运算符。在 Perl(从 C 复制而来)中,~
已经用于“位补码”,因此他们使用了 =~
。后来的符号被复制到其他几种语言。 (Perl 5.10 和 Perl 6 更喜欢 ~~
,但这在这里没有影响。)我想您可以将其视为某种近似相等...
这是另一种看法(仅使用 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))"
在脚本中的任何地方都不需要大括号。
test
似乎不支持这一点。 [[
确实如此。 [[ 16#aa -eq 16#aa ]] && echo integer
打印“整数”。
[[
会为此方法返回误报;例如 [[ f -eq f ]]
成功。所以它必须使用 test
或 [
。
您可以去除非数字并进行比较。这是一个演示脚本:
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
${var//string}
和 ${var#string}
的信息,并在“模式匹配”部分中查看 [^[:digit:]]`(也包括在 man 7 regex
中)。
match=${match#0*}
不去除前导零,它最多去除一个零。使用扩展只能通过 match=${match##+(0)}
使用 extglob
来实现。
09
不是整数。测试是输入 (09
) 是否等于经过清理的版本(9
- 一个整数),而它不等于。
对我来说,最简单的解决方案是在 (())
表达式中使用变量,如下所示:
if ((VAR > 0))
then
echo "$VAR is a positive integer."
fi
当然,此解决方案仅在零值对您的应用程序没有意义的情况下才有效。在我的情况下恰好是这样,这比其他解决方案要简单得多。
正如评论中所指出的,这会使您受到代码执行攻击:(( ))
运算符评估 VAR
,如 the bash(1) man page 的 Arithmetic Evaluation
部分所述。因此,当 VAR
的内容来源不确定时,不应使用此技术(当然,也不应使用任何其他形式的变量扩展)。
if (( var )); then echo "$var is an int."; fi
变得更简单
VAR='a[$(ls)]'; if ((VAR > 0)); then echo "$VAR is a positive integer"; fi
。此时您很高兴我没有输入一些邪恶的命令而不是 ls
。因为 OP 提到了用户输入,所以我真的希望您不要在生产代码中将它与用户输入一起使用!
agent007
或使用 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
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
补充 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]
...?
为了笑,我大致只是快速制定了一组函数来执行此操作(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 剥离),最好将它们剥离
不定期副业成功案例分享
+
表示“前面的一个或多个”,$
表示输入模式的结束。所以正则表达式匹配一个可选的-
后跟一个或多个十进制数字。[A-z]
不仅会给出A-Z
和a-z
,还会给出\
、[
、]
、^
、_
和`
。d[g-i]{2}
的内容最终可能不仅匹配dig
,而且匹配该答案建议的排序规则中的dish
(其中sh
有向图被视为单个字符,在h
之后整理)。