在Bash的if语句中进行正则表达式匹配

155

我在这里做错了什么?

试图匹配包含空格、小写字母、大写字母或数字的任何字符串。特殊字符也可以,但我认为这需要转义某些字符。

TEST="THIS is a TEST title with some numbers 12345 and special char *&^%$#"

if [[ "$TEST" =~ [a-zA-Z0-9\ ] ]]; then BLAH; fi

这显然只是测试大写字母、小写字母、数字和空格。但是它不起作用。
更新:
我想我应该更具体一些。这是实际的代码行:
if [[ "$TITLE" =~ [a-zA-Z0-9 $%^\&*#] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; fi

这是错误的内容:
./anm.sh: line 265: syntax error in conditional expression
./anm.sh: line 265: syntax error near `&*#]'
./anm.sh: line 265: `  if [[ ! "$TITLE" =~ [a-zA-Z0-9 $%^\&*#] ]]; then RETURN="FAIL" && ERROR="ERROR: Title can only contain upper and lowercase letters, numbers, and spaces!"; return; fi'

你实际上使用的是哪个 shell?/bin/sh?/bin/bash?/bin/csh? - Willem Van Onsem
12
最好将正则表达式放在一个变量里。re='...whatever...'; [[ $string =~ $re ]](不要加引号--这是少数几种会破坏可以不用引号就能工作的情况之一)。 - Charles Duffy
5
请将赋值语句用单引号括起来,双引号无法正确保护特殊字符。 - tripleee
3
非常感谢,查尔斯!虽然不将其放入变量中仍然可以,但绝不能加引号!例如:[[ $var =~ .* ]] 用于匹配正则表达式 .*(任何内容)。我猜如果你使用引号,那么引号本身会被视为正则表达式的一部分... - Stéphane
14
摘要如下:(1)使用单引号将模式保存到变量中,例如pattern='^hello[0-9]*$';(2)如果需要进行正则表达式匹配,请勿在双方括号表达式中引用模式,因为引用会禁用正则表达式匹配。即表达式[[ "$x" =~ $pattern ]]将使用正则表达式进行匹配,而表达式[[ "$x" =~ "$pattern" ]]会禁用正则表达式匹配,并等同于[[ "$x" == "$pattern" ]] - Trevor Boyd Smith
4个回答

267
关于bash的[[ ]]结构,有几个重要的事情需要知道。第一:
单词拆分和路径名扩展不会在[[和]]之间的单词上执行;会执行tilde扩展、参数和变量扩展、算术扩展、命令替换、进程替换和引号移除。
第二件事:
还有一个附加的二进制运算符“=~”,...运算符右侧的字符串被视为扩展正则表达式并相应地匹配... 模式的任何部分都可以加引号以强制将其作为字符串匹配
因此,在=~的任一侧的$v将扩展为该变量的值,但结果不会被拆分或扩展成路径名。换句话说,在左侧保留未引用的变量扩展是完全安全的,但您需要知道变量扩展将发生在右侧。
所以如果您写:[[ $x =~ [$0-9a-zA-Z] ]],右侧正则表达式中的$0将在解释正则表达式之前扩展,这可能会导致正则表达式无法编译(除非$0的扩展以小于数字的标点符号或ASCII值结尾)。如果像这样引用右侧[[ $x =~ "[$0-9a-zA-Z]" ]],则右侧将被视为普通字符串而不是正则表达式(并且$0仍将被扩展)。在这种情况下,您真正想要的是:[[ $x =~ [\$0-9a-zA-Z] ]] 同样,在[[和]]之间的表达式在解释正则表达式之前会被拆分成单词。因此,正则表达式中的空格需要转义或加引号。如果要匹配字母、数字或空格,可以使用:[[ $x =~ [0-9a-zA-Z\ ] ]]。其他字符也需要转义,例如#,如果没有引号,则会开始注释。当然,您可以将模式放入变量中:
pat="[0-9a-zA-Z ]"
if [[ $x =~ $pat ]]; then ...

对于包含许多字符需要在bash解析器中进行转义或引用的正则表达式,许多人更喜欢这种风格。但要注意:在这种情况下,您不能引用变量扩展:

# This doesn't work:
if [[ $x =~ "$pat" ]]; then ...

最后,我认为你想要做的是验证变量只包含有效的字符。最简单的方法是确保它不包含无效的字符。换句话说,可以使用如下表达式进行检查:

valid='0-9a-zA-Z $%&#' # add almost whatever else you want to allow to the list
if [[ ! $x =~ [^$valid] ]]; then ...

!表示否定测试,将其转换为“不匹配”运算符,[^...]正则表达式字符类表示“除了...之外的任何字符”。

参数扩展和正则表达式运算符的组合可以使Bash正则表达式语法“几乎可读”,但仍然存在一些陷阱(总是有吧?)。其中一个是,即使在$valid被引用的情况下,也不能将]放入其中,除非它位于开头。 (这是Posix正则表达式规则:如果要在字符类中包括],则需要将其放在开头。-可以放在开头或结尾,因此如果您需要]-,则需要以]开头并以-结尾,从而得到正则表达式“我知道我在做什么”的表情符号:[][-]


11
只想指出_"!是“不匹配”操作符"_是不正确的。可以使用`if ! [[ $x = $y ]]或者if [[ ! $x =~ $y ]]`。请注意不要改变原文意思,同时保持通俗易懂。 - alcohol
4
@leonard: 我的陈述“你不能引用变量扩展”,和评论“这不起作用”的意思有什么不同?有什么不清楚的地方吗? - rici
确实,有你在,@rici。这篇文章对我来说非常宝贵。我尝试了单引号和双引号,但我没有想到完全去掉引号。哦,Bash! - orion elenzil
1
@jinbeomhong:表达式本身通常使用空格分隔成单词。但是参数和命令扩展不会被拆分成单词。 - rici
1
@jinbeomhong:我说的和bash手册没有什么不同。“[[”和“]]”之间的单词会从程序文本中解析出来,就像命令行被解析成单词一样。但与命令行不同的是,在扩展后,这些单词不会被分割。 - rici
显示剩余8条评论

59

如果有人想要一个使用变量的示例...

#!/bin/bash

# Only continue for 'develop' or 'release/*' branches
BRANCH_REGEX="^(develop$|release//*)"

if [[ $BRANCH =~ $BRANCH_REGEX ]];
then
    echo "BRANCH '$BRANCH' matches BRANCH_REGEX '$BRANCH_REGEX'"
else
    echo "BRANCH '$BRANCH' DOES NOT MATCH BRANCH_REGEX '$BRANCH_REGEX'"
fi

14

我更喜欢使用[:punct:]来表示那个。而且,a-zA-Z09-9可以简写为[:alnum:]

[[ $TEST =~ ^[[:alnum:][:blank:][:punct:]]+$ ]]

6

你可能正在看这个问题,因为你犯了一个像我一样愚蠢的拼写错误,把“=~”和“~=”弄反了。


似乎模式必须以 ^ 开头并以美元符号 $ 结尾才能正常工作。这是唯一的方法,使得结果的真值对我来说是正确的。 - von spotz
我在JavaScript中仍然经常出错。 - Sridhar Sarnobat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接