2

在 Linux bash shell 中,可以使用 [[ 命令来进行判断。
其中,可以使用 [[ 命令的 =~ 操作符来判断某个字符串是否包含特定模式。

查看 man bash 对 [[ 命令的 =~ 操作符说明如下:

An additional binary operator, =~, is available, with the same precedence as == and !=.
When it is used, the string to the right of the operator is considered an extended regular expression and matched accordingly.
The return value is 0 if the string matches the pattern, and 1 otherwise.
If the regular expression is syntactically incorrect, the conditional expression's return value is 2.

Any part of the pattern may be quoted to force the quoted portion to be matched as a string.

即,使用 =~ 操作符时,其右边的字符串被认为是一个扩展正则表达式。
扩展之后跟左边字符串进行比较,看左边字符串是否包含指定模式。
注意是包含关系,不是完整匹配。
也就是判断右边的模式是否为左边字符串的子字符串,而不是判断右边的模式是否完全等于左边字符串。

这里面提到一个非常关键的点,在所给的扩展正则表达式中,用引号括起来的部分会被当成字符串,不再被当成正则表达式。
如果 =~ 操作符右边的字符串都用引号括起来,那么表示匹配这个字符串自身的内容,不再解析成正则表达式。

如果想要 =~ 操作符右边的字符串被当成正则表达式来处理,一定不能用引号把整个字符串括起来。
无论是双引号、还是单引号都不要加。
这是常见的使用误区,后面会举例说明。

注意:只有 [[ 命令支持 =~ 操作符,test 命令和 [ 命令都不支持 =~ 操作符。

判断字符串是否全是数字

下面用 =~ 操作符来判断一个字符串是否全是数字。

假设有一个 checkdigits.sh 脚本,内容如下:

#!/bin/bash

function check_digits()
{
    local count=${#1}
    if [[ "$1" =~ [0-9]{$count} ]]; then
        echo "All digits."
    else
        echo "Not all digits."
    fi
}

check_digits "$1"

该脚本定义了一个 check_digits 函数。
这个函数使用 ${#1} 参数扩展表达式获取所传入第一个参数的字符串长度,并赋值给 count 变量。

在正则表达式中,[0-9] 表示匹配 0 到 9 之间的任意一个数字,但是只匹配一个数字。
[0-9]{n} 表示匹配 n 个连续的数字。

[[ "$1" =~ [0-9]{$count} ]] 表达式中,用 =~ 操作符判断第一个参数值是否精确匹配 count 个连续的数字。
如果是,就说明第一个参数对应的字符串全是数字,否则不全是数字。

执行 checkdigits.sh` 脚本的结果如下:

$ ./checkdigits.sh 12345
All digits.
$ ./checkdigits.sh abcd
Not all digits.
$ ./checkdigits.sh a2c
Not all digits.
$ ./checkdigits.sh 1b3
Not all digits.

可以看到,传入的参数全是数字时,才会打印 "All digits."。
传入全字母、或者字母和数字的组合,能够正确判断到不全是数字,会打印 "Not all digits."。

由于 =~ 操作符右边的参数是扩展正则表达式,如果不熟悉正则表达式的话,在使用时会遇到一些不预期的异常。
下面举例说明判断字符串是否全是数字的一些错误写法,注意避免出现这类错误。

错误写法一

假设有一个 checkdigits_fake.sh 脚本,内容如下:

#!/bin/bash

function check_digits()
{
    if [[ "$1" =~ [0-9] ]]; then
        echo "All digits."
    else
        echo "Not all digits."
    fi
}

check_digits "$1"

这个脚本在 =~ 操作符右边提供的正则表达式是 [0-9],对应 0 到 9 之间任意一个数字,但是只对应一个数字。
那么 [[ "$1" =~ [0-9] ]] 是判断传入的第一个参数是否包含一个数字。
只要有一个数字,就会返回为 true。
它并不能判断出所有字符是否都是数字。

具体执行结果如下:

$ ./checkdigits_fake.sh 12345
All digits.
$ ./checkdigits_fake.sh abcd
Not all digits.
$ ./checkdigits_fake.sh 1b3d
All digits.
$ ./checkdigits_fake.sh a2
All digits.

可以看到,只有当传入的参数全是字母时,才会打印 "Not all digits."。
传入全数字、或者数字和字母的组合,都会打印 "All digits."。
这个脚本不能准确地判断字符是否全是数字。

错误写法二

checkdigits_fake.sh 脚本修改成下面的内容:

#!/bin/bash

function check_digits()
{
    if [[ "$1" =~ [0-9]* ]]; then
        echo "All digits."
    else
        echo "Not all digits."
    fi
}

check_digits "$1"

即,用 [0-9]* 来表示匹配零个或多个连续的数字。

从字面上看像是可以匹配到全是数字的情况。
但实际上,它还是会匹配一个数字的情况。
只要有一个数字就会认为匹配,甚至还会匹配没有数字的情况。

具体的执行结果如下:

$ ./checkdigits_fake.sh 12345
All digits.
$ ./checkdigits_fake.sh abcd
All digits.
$ ./checkdigits_fake.sh 1b3d
All digits.
$ ./checkdigits_fake.sh a2
All digits.

可以看到,无论传入的参数是全数字、全字母、还是数字和字母的组合,都是打印 "All digits.",都符合所给的 [0-9]* 这个模式。
即,这个脚本也达不到判断字符串是否全是数字的效果。

类似的,[0-9]+ 表示匹配一个或多个连续的数字。
使用这个模式也不能判断字符串是否全是数字。

错误写法三

前面提到,如果把 =~ 操作符右边的字符串都用双引号括起来,那么表示匹配这个字符串自身的内容,不再解析成正则表达式。
例如 [0-9] 在正则表达式中对应一个数字。
但是 "[0-9]" 对应的是 "[0-9]" 这个字符串,不再对应一个数字。

虽然上面的 [[ "$1" =~ [0-9]{$count} ]] 表达式可以正确判断出字符串是否都是数字。
一旦用双引号把 [0-9]{$count} 括起来,写成 [[ "$1" =~ "[0-9]{$count}" ]],就会判断出错。
可以自行修改 checkdigits.sh 脚本代码进行验证。

下面用其他例子进行举例说明:

$ [[ "123" =~ [0-9]{3} ]]; echo $?
0
$ [[ "123" =~ "[0-9]{3}" ]]; echo $?
1
$ [[ "[0-9]{3}" =~ [0-9]{3} ]]; echo $?
1
$ [[ "[0-9]{3}" =~ "[0-9]{3}" ]]; echo $?
0

可以看到,[[ "123" =~ [0-9]{3} ]] 正确地判断出 "123" 字符串包含三个连续的数字。
echo $? 打印命令返回值是 0,也就是 true。

[[ "123" =~ "[0-9]{3}" ]] 命令的返回值是 1,对应 false,认为要比较的两个字符串不匹配。

"[0-9]{3}" 此时不再表示匹配三个连续的数字,而是匹配 "[0-9]{3}" 这个字符串自身。

[[ "[0-9]{3}" =~ [0-9]{3} ]] 命令中,右边的 [0-9]{3} 没加双引号,会按照正则表达式来解析,表示匹配三个连续的数字。
而左边字符串并没有三个连续的数字,所以返回 1,不匹配。

[[ "[0-9]{3}" =~ "[0-9]{3}" ]] 命令中,右边的 "[0-9]{3}" 加了双引号,不再当成正则表达式处理。
这只会比较字符串自身,所以返回 0,是匹配的。

在 bash 中,为了避免单词拆分导致不预期的行为,一般都会用双引号把字符串、或者变量值括起来。
但是在使用 =~ 操作符时,注意检查右边字符串是否要当成正则表达式来处理。
如果是,不要加双引号。

判断某个字符串是否为另一个字符串的子字符串

我们可以使用 =~ 操作来判断某个字符串是否为另一个字符串的子字符串。
要判断的字符串要写在操作符右边,被判断的字符串要写在操作符的左边。

假设有一个 check_substr.sh 脚本,内容如下:

#!/bin/bash

function check_substr()
{
    if [[ "$1" =~ "$2" ]]; then
        echo \"$1\" contains \"$2\"
    else
        echo \"$1\" does not contain \"$2\"
    fi
}

check_substr "$1" "$2"

这个脚本判断传入的第二个参数是否为第一个参数的子字符串。

具体执行结果如下:

$ ./check_substr.sh "This is a test string"  "test string"
"This is a test string" contains "test string"
$ ./check_substr.sh "This is a test string"  "is a test"
"This is a test string" contains "is a test"
$ ./check_substr.sh "This is a test string"  "isa test"
"This is a test string" does not contain "isa test"
$ ./check_substr.sh "This is a test string"  "new string"
"This is a test string" does not contain "new string"

测试的时候,如果传入的字符串参数包含空格,要用双引号括起来。
注意=~ 右边的 "$2" 加了双引号,不再当成正则表达式处理,只会比较字符串自身的内容。


霜鱼片
446 声望331 粉丝

解读权威文档,编写易懂文章。如有恰好解答您的疑问,多谢赞赏支持~