bash是否支持单词边界正则表达式?

sta*_*fry 27 regex bash

我试图匹配列表中的单词的存在,然后再次添加该单词(以避免重复).我正在使用bash 4.2.24并尝试以下方法:

[[  $foo =~ \bmyword\b ]]
Run Code Online (Sandbox Code Playgroud)

[[  $foo =~ \<myword\> ]]
Run Code Online (Sandbox Code Playgroud)

但是,似乎都不起作用.它们在bash docs示例中提到:http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_01.html.

我认为我做错了什么,但我不确定是什么.

mkl*_*nt0 32

TL;博士

  • 为了安全起见,不要使用正则表达式的文字=~.
    相反,使用:

  • 是否\b\</或\>完全支持取决于主机平台,而不是Bash:

    • 他们在Linux上工作,
    • 但不是基于BSD的平台,如macOS ; 在那里,使用[[:<:]][[:>:]]替代,在一个不带引号的正则表达式字面值的上下文中,必须被转义为[[:\<:]][[:\>:]] ; 以下按预期工作,但仅适用于BSD/macOS:
      • [[ ' myword ' =~ [[:\<:]]myword[[:\>:]] ]] && echo YES # OK
  • 就不会出现问题 -在任何平台上- 如果你限制你的正则表达式来在构建POSIX ERE(扩展正则表达式)规范.

    • 不幸的是,POSIX ERE 支持字边界断言,尽管你可以模仿它们 - 参见最后一节.

    • 与在macOS上一样, \支持任何前缀构造,因此也可以使用方便的字符类快捷方式,例如\s\w.

    • 然而,最重要的是,这种符合ERE标准的正则表达式可移植的(例如,在Linux和macOS上工作)

=~是一种罕见的情况(唯一的情况?)内置 Bash功能,其行为依赖于平台:它使用运行它的平台的正则表达式库,从而在不同平台上产生不同的正则表达式风格.

因此,它通常是非平凡的,并且需要额外注意编写使用运算符的可移植代码=~. 坚持使用POSIX ERE是唯一可行的方法,这意味着您必须解决其局限性 - 请参阅底部部分.

如果您想了解更多信息,请继续阅读.


巴蜀V3.2 +(除非该compat31 shopt选项设置),在的RHS(右侧操作数)=~运营商必须加引号才能被识别为正则表达式(如果你引用正确的操作,=~进行定期的字符串比较,而不是).

更准确地说,至少特殊的正​​则表达式字符和序列必须是不加引号的,所以引用那些应该从字面上理解的子字符串是可行的和有用的; 例如,匹配,因为是不加引号,因此被正确识别为字符串起始锚,而通常是特殊的正则表达式字符,由于引用而字面上匹配.[[ '*' =~ ^'*' ]]^*

然而,似乎是一个设计限制在(至少)bash 3.x禁止使用的\-prefixed正则表达式构建体(例如,\<,\>,\b,\s,\w在A,...)文字 =~ RHS ; 该限制会影响Linux,而BSD/macOS版本不会受到影响,因为基本上不支持任何\前缀的正则表达式构造:

# 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
Run Code Online (Sandbox Code Playgroud)

问题:

Fólkvangr的提示是他的投入.

一个文字 RHS的=~是设计解析不同比未加引号的令牌作为参数,以试图使用户专注于转义字符,正则表达式,而不同时不必担心平时逸出在不带引号标记规则.

例如,

[[ 'a[b' =~ a\[b ]] && echo MATCHES  # OK
Run Code Online (Sandbox Code Playgroud)

匹配,因为它\被_passsed到正则表达式引擎(也就是说,正则表达式引擎也看到文字 a\[b),而如果你使用相同的未引用的标记作为常规参数,通常应用于未加引号的标记的shell扩展会"吃掉" \,因为它被解释为shell转义字符:

$ printf %s a\[b
a[b  # '\' was removed by the shell.
Run Code Online (Sandbox Code Playgroud)

然而,在的情况下=~这个特殊穿过的\仅仅是人物之前应用正则表达式的元字符本身被定义,ERE(扩展正则表达式)POSIX规范(为了逃避他们的正则表达式,使他们作为文字处理:
\ ^ $ [ { . ? * + ( ) |
相反,这些正则表达式元字符可以特别使用不带引号 - 并且实际上必须保持不带引号以具有其特殊的正则表达式含义 - 即使它们中的大多数通常需要\- 在不带引号的令牌中进行处理以防止shell解释它们.
但是,一个子集的的外壳元字符仍然需要转义,在外壳的缘故,以免打破的语法[[ ... ]]条件:
& ; < > space
由于这些字符是不是也正则表达式元字符,没有必要也支持逃离他们在正则表达式方面,例如,\&正如在RHS中看到的正则表达式引擎一样&正常工作.

对于任何其他由前面字符\,壳移除\发送字符串到正则表达式引擎之前(如它正常壳膨胀期间所做的),这是不幸的,因为然后甚至认为壳字符考虑特殊不能作为被传递\<char>到正则表达式引擎,因为shell总是将它们传递给它们<char>.
例如,\b总是被b正则表达式引擎看作.

因此,目前不可能使用(根据定义非POSIX)正则表达式的形式构造\<char>(例如\<,\>,\b,\s,\w,\d在字面,不带引号的,...)=~RHS,因为没有逃脱的形式可以确保这些结构在被shell解析之后,正则表达式引擎会看到这样:

由于既不<,>也不b正则表达式元字符,壳移除\\<,\>,\b(如在常规shell膨胀发生).因此,传递\<word\>,例如,使正则表达式引擎看到<word>,这不是意图:

  • [[ '<word>' =~ \<word\> ]] && echo YES匹配,因为正则表达式引擎看到<word>.
  • [[ 'boo' =~ ^\boo ]] && echo YES匹配,因为正则表达式引擎看到^boo.

试图\\<word\\> 中断的命令,这是因为每对待\\作为逃脱\,这意味着元字符<,然后考虑没有引用的,引起一个语法错误:

  • [[ ' word ' =~ \\<word\\> ]] && echo YES 导致语法错误.
  • 这不会发生\\b,但\\b通过(由于\前面的正则表达式metachar \),这也不起作用:
    • [[ '\boo' =~ ^\\boo ]] && echo YES匹配,因为正则表达式引擎看到\\boo,匹配文字\boo.

尝试\\\<word\\\>- 通过正常的 shell扩展规则导致\<word\>(尝试printf %s \\\<word\\\>) - 不起作用:

  • 所发生的是,该壳\\<(同上,用于\b和其他\-prefixed序列),然后经过前述\\通过对正则表达式引擎原样(再次,因为\一个之前被保留的正则表达式元字符):

  • [[ ' \<word\> ' =~ \\\<word\\\> ]] && echo YES匹配,因为正则表达式引擎看到\\<word\\>,匹配文字\<word\>.

简而言之:

  • 的bash的解析=~RHS 文字的设计采用了单字符记正则表达式元字符,并且不支持多字符结构与启动\,如\<.

    • 因为POSIX ERE不支持这样的构造,所以=~如果你将自己限制在这样的正则表达式,那么就可以按照设计工作.

    • 然而,即使在这种约束条件下,由于需要混合使用与正则表达式相关的和与shell相关的 - \引用(引用),因此设计有点笨拙.

    • Fólkvangr在这里找到了Bash常见问题解答中的官方设计原理,然而,它既没有解决所谓的尴尬,也没有解决(总是非POSIX)\<char>正则表达式构造的缺乏支持; 确实提到使用辅助.然而,变量作为一种变通方法,尽管只是为了更容易表示空白.

  • 如果正则表达式引擎应该看到的字符串是通过变量或通过命令替换的输出提供的,那么所有这些解析问题都会消失,如上所示.


可选读取:使用符合POSIX的ERE(扩展正则表达式)对字边界断言进行可移植仿真:

  • (^|[^[:alpha:][:digit:]_])而不是\</[[:<:]]

  • ([^[:alpha:][:digit:]_]|$)而不是\>/[[:>:]]

注意:\b无法使用SINGLE表达式进行模拟 - 在适当的位置使用上述内容.

潜在的警告是,上述表达式也将捕获匹配的非单词字符,而真正的断言\</ [[:<:]]和不捕获.

$foo = 'myword'
[[ $foo =~ (^|[^[:alpha:][:digit:]_])myword([^[:alpha:][:digit:]_]|$) ]] && echo YES
Run Code Online (Sandbox Code Playgroud)

以上匹配,如预期.

  • 这应该是公认的答案,因为它适用于 POSIX 系统。 (2认同)
  • 我花了大约一个小时寻找这个答案。应该是解决办法!谢谢。 (2认同)
  • 令人难以置信的全面且有效的答案。应该是公认的答案。谢谢你! (2认同)

Edu*_*nec 27

是的,支持所有列出的正则表达式扩展,但在使用之前将模式放在变量中会更好.试试这个:

re=\\bmyword\\b
[[ $foo =~ $re ]]
Run Code Online (Sandbox Code Playgroud)

我四处寻找这个问题,他的答案似乎解释了为什么当正则表达式以内联方式编写时,行为会发生变化.

编者注:相关问题并未解释OP的问题; 它只是解释了从Bash版本3.2正则表达式开始(或者至少是特殊的正则表达式字符符号)默认情况下必须不加引号来对待它 - 这正是OP所尝试的.
但是,这个答案的解决方法有效的.

您可能必须重写测试以便为正则表达式使用临时变量,或使用3.1兼容模式:

shopt -s compat31
Run Code Online (Sandbox Code Playgroud)

  • +1.或者,您可以使用命令替换:`[[$ foo =〜$(echo'\ <myword \>')]]`.它仍然令人烦恼,但至少不需要杂散变量. (5认同)

Wei*_*nde 6

不完全是“\b”,但对我来说比其他建议更具可读性(和便携性):

[[  $foo =~ (^| )myword($| ) ]]
Run Code Online (Sandbox Code Playgroud)


Col*_*zer 5

公认的答案集中在使用辅助变量来处理Bash [[ ... ]]表达式中正则表达式的语法怪异。很好的信息。

但是,真正的答案是:

\b \<并且\>不能在bash版本4.3.42(1)-发行版(x86_64-apple-darwin15.0.0)的OS X 10.11.5(El Capitan)上使用。

而是使用[[:<:]][[:>:]]