tl;dr
-
为了安全起见,不要使用正则表达式literal with =~
.
相反,使用:
-
either: an 辅助变量 - see @Eduardo Ivancec 的回答 https://stackoverflow.com/a/9793094/45375.
-
or: 输出字符串文字的命令替换 - see @ruakh 对 @Eduardo Ivancec 的回答的评论 https://stackoverflow.com/q/9792702/45375#comment-12473988
- 请注意,两者必须使用unquoted as the
=~
RHS.
-
Whether \b
and \<
/ \>
are 完全支持取决于主机平台,不是 Bash:
- 他们确实致力于Linux,
- but NOT on BSD-based platforms such as macOS; there, use
[[:<:]]
and [[:>:]]
instead, which, in the context of an unquoted regex literal, must be escaped as [[:\<:]]
and [[:\>:]]
; the following works as expected, but only on BSD/macOS:
[[ ' myword ' =~ [[:\<:]]myword[[:\>:]] ]] && echo YES # OK
-
The 问题就不会出现- 在任何平台上 -如果您将正则表达式限制为中的构造POSIX ERE(扩展正则表达式)规范 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04.
不幸的是,POSIX ERE 确实not支持字边界断言,尽管您可以emulate他们 - 请参阅最后一节。
与 macOS 上一样,no \
支持前缀结构,因此可以使用方便的字符类快捷方式,例如\s
and \w
也不可用。
然而,好的一面是,这样符合 ERE 的正则表达式那么是portable(例如,可在 Linux 和 macOS 上运行)
=~
是罕见的情况(唯一的情况?)built-inBash 功能的行为是依赖于平台:它使用其运行平台的正则表达式库,从而导致不同平台上的不同正则表达式风格.
因此,它是通常并不简单,需要额外小心编写portable使用的代码=~
操作员.
坚持使用 POSIX ERE 是唯一可靠的方法,这意味着您必须解决它们的局限性 - 请参阅底部部分。
如果您想了解更多信息,请继续阅读。
在 Bash v3.2+ 上(除非compat31
shopt
选项已设置),RHS(右侧操作数)=~
运算符必须是unquoted为了被认可为regex (如果你引用正确的操作数,=~
执行常规字符串比较反而)。
More accurately, at least the special regex characters and sequences must be unquoted, so it's OK and useful to quote those substrings that should be taken literally; e.g., [[ '*' =~ ^'*' ]]
matches, because ^
is unquoted and thus correctly recognized as the start-of-string anchor, whereas *
, which is normally a special regex char, matches literally due to the quoting.
然而,似乎有一个设计限制在(至少)bash 3.x
that 阻止使用\
-前缀正则表达式构造(例如,\<
, \>
, \b
, \s
, \w
, ...) 在一个literal =~
RHS;限制affects Linux,而 BSD/macOS 版本是not受到影响,因为根本不支持任何\
-前缀正则表达式构造:
# Linux only:
# PROBLEM (see details further below):
# Seen by the regex engine as: <word>
# The shell eats the '\' before the regex engine sees them.
[[ ' word ' =~ \<word\> ]] && echo MATCHES # !! DOES NOT MATCH
# Causes syntax error, because the shell considers the < unquoted.
# If you used \\bword\\b, the regex engine would see that as-is.
[[ ' word ' =~ \\<word\\> ]] && echo MATCHES # !! BREAKS
# Using the usual quoting rules doesn't work either:
# Seen by the regex engine as: \\<word\\> instead of \<word\>
[[ ' word ' =~ \\\<word\\\> ]] && echo MATCHES # !! DOES NOT MATCH
# WORKAROUNDS
# Aux. viarable.
re='\<word\>'; [[ ' word ' =~ $re ]] && echo MATCHES # OK
# Command substitution
[[ ' word ' =~ $(printf %s '\<word\>') ]] && echo MATCHES # OK
# Change option compat31, which then allows use of '...' as the RHS
# CAVEAT: Stays in effect until you reset it, may have other side effects.
# Using (...) around the command confines the effect to a subshell.
(shopt -s compat31; [[ ' word ' =~ '\<word\>' ]] && echo MATCHES) # OK
问题:
Tip of the hat to Fólkvangr https://stackoverflow.com/users/9657437/f%c3%b3lkvangr for his input.
A literal RHS of =~
是按设计解析的不同地比不带引号的标记作为参数,在尝试让用户专注于转义字符just为了regex,也不必担心平常的shell未加引号的标记中的转义规则。
例如,
[[ 'a[b' =~ a\[b ]] && echo MATCHES # OK
匹配,因为\
被_传递到正则表达式引擎(也就是说,正则表达式引擎也看到literal a\[b
),而如果您使用相同的不带引号的标记作为常规参数,则通常的外壳扩展 http://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html#Shell-Expansions应用于未加引号的标记会“吃掉”\
,因为它被解释为shell转义字符:
$ printf %s a\[b
a[b # '\' was removed by the shell.
然而,在这样的背景下=~
这次特殊的穿越\
仅适用于以下字符之前regex元字符通过他们自己,定义为ERE(扩展正则表达式)POSIX 规范 https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04_03(为了躲避他们对于正则表达式,以便将它们视为literals:
\ ^ $ [ { . ? * + ( ) |
相反,这些正则表达式元字符可能会被例外地使用unquoted- 确实must不被引用以拥有其特殊的regex含义 - 尽管大多数通常需要\
-转义未加引号的标记以防止shell从解释它们。
Yet, a subset of the shell元字符do仍然需要逃避,因为shell's sake,以免破坏语法[[ ... ]]
有条件:
& ; < > space
因为这些角色也不是regex元字符,不需要在正则表达式方面也支持转义它们,因此,例如,正则表达式引擎看到\&
在 RHS 中就像&
工作正常。
For any other前面的字符\
, 贝壳removes the \
在将字符串发送到正则表达式引擎之前(就像在正常 shell 扩展期间所做的那样),这是不幸的,因为然后甚至是 shell 中的字符doesn't认为特殊不能传递为\<char>
到正则表达式引擎,因为 shell 总是将它们作为<char>
.
E.g, \b
总是被视为只是b
由正则表达式引擎。
因此,目前不可能使用以下形式的(根据定义非 POSIX)正则表达式构造:\<char>
(e.g., \<
, \>
, \b
, \s
, \w
, \d
, ...) 按字面意思,不加引号=~
RHS, 因为任何形式的转义都不能确保这些构造被regex发动机本身,经过解析后shell:
既然两者都没有<
, >
, nor b
are regex元字符,外壳removes the \
from \<
, \>
, \b
(如常规 shell 扩展中所发生的那样)。因此,通过\<word\>
例如,使正则表达式引擎看到<word>
,这不是本意:
-
[[ '<word>' =~ \<word\> ]] && echo YES
匹配,因为正则表达式引擎看到<word>
.
-
[[ 'boo' =~ ^\boo ]] && echo YES
匹配,因为正则表达式引擎看到^boo
.
Trying \\<word\\>
breaks该命令,因为shell对待每一个\\
作为一个逃亡者\
,这意味着元字符<
然后考虑unquoted,导致语法错误:
-
[[ ' word ' =~ \\<word\\> ]] && echo YES
导致语法错误。
- This wouldn't happen with
\\b
, but \\b
is passed through (due to the \
preceding a regex metachar, \
), which also doesn't work:
-
[[ '\boo' =~ ^\\boo ]] && echo YES
匹配,因为正则表达式引擎看到\\boo
,与文字匹配\boo
.
Trying \\\<word\\\>
- 由normalshell 扩展规则导致\<word\>
(try printf %s \\\<word\\\>
) - also不起作用:
简而言之:
可选读物:便携式仿真使用符合 POSIX 的 ERE(扩展正则表达式)进行字边界断言:
Note: \b
不能用单个表达式来模拟 - 在适当的地方使用上面的表达式。
潜在的警告是上述表达式也将capture匹配的非单词字符,而 true断言例如\<
/ [[:<:]]
也不要。
$foo = 'myword'
[[ $foo =~ (^|[^[:alnum:]_])myword([^[:alnum:]_]|$) ]] && echo YES
上面的匹配,符合预期。