为什么不按字面意思以全局模式处理“|”?

为什么不按字面意思以全局模式处理“|”?

我的问题来自将正则表达式存储在 shell 变量中如何避免引用 shell 特有字符的问题?

  1. 为什么会出现错误:

    $ [[ $a = a|b ]]  
    bash: syntax error in conditional expression: unexpected token `|'
    bash: syntax error near `|b'
    

    [[ ... ]]的第二个操作数内部=预计是一个通配模式。

    不是a|b有效的通配符模式吗?你能指出它违反了哪条语法规则吗?

  2. 下面的一些评论指出它|被解释为管道。

    然后将=glob 模式更改为=~regex 模式使 |工作

    $ [[ $a =~ a|b ]]
    

    我从中学到了学习狂欢p180 在我之前的帖子|在解释开始时,甚至在任何其他解释步骤(包括解析示例中的条件表达式)之前,它都被识别为管道。那么如何 |在使用时被识别为正则表达式运算符=~,而不会像使用时一样在无效使用时被识别为管道=呢?这让我认为第 1 部分中的语法错误并不意味着|被解释为管道。

    shell 从标准输入或脚本读取的每一行称为管道;它包含一个或多个由零个或多个竖线字符 (|) 分隔的命令。对于它读取的每个管道,shell 将其分解为命令,设置管道的 I/O,然后对每个命令执行以下操作(图 7-1):

谢谢。

答案1

没有充分的理由

[[ $a = a|b ]]

应该报告错误而不是测试 $a 是否是字符串a|b,而 while[[ $a =~ a|b ]]不返回错误。

唯一的原因是它|通常(外部和内部[[ ... ]])是一个特殊字符。在那个[[ $a =位置上,bash需要一种正常的令牌类型单词就像普通 shell 命令行中的参数或重定向目标一样(但就像extglob自 bash 4.1 以来已启用该选项一样)。

(经过单词在这里,我指的是单词在假设的 shell 语法中,例如POSIX 规范描述的一种,shell 会将其解析为简单 shell 命令行中的一个标记,而不是其他单词定义,例如英语字母序列或非空格字符序列。foo"bar baz", $(echo x y), 是这样两个单词s)。

在普通的 shell 命令行中:

echo a|b

通过echo a管道传送到b.a|b不是一个单词,它是三个标记:aa 单词,一个|令牌和一个b 单词令牌。

当用于 时[[ $a = a|b ]]bash期望单词它得到 ( a),但随后发现一个意外的|标记,导致错误。

有趣的是,bash没有抱怨:

[[ $a = a||b ]]

因为它现在是一个a标记,后面跟着一个||标记,后面跟着b,所以它的解析方式与:

[[ $a = a || b ]]

这是测试字符串是否$a为空。ab

现在,在:

[[ $a =~ a|b ]]

bash不能有相同的解析规则。具有相同的解析规则意味着上面的内容会出错,并且需要引用该规则|以确保a|b是单个单词。但是,从 bash 3.2 开始,如果你这样做:

[[ $a =~ 'a|b' ]]

这不再是针对a|b正则表达式匹配,而是针对a\|b正则表达式匹配。也就是说,shell 引用具有删除正则表达式运算符的特殊含义的副作用。这是一项功能,因此行为与[[ $a = "?" ]]此类似,但通配符模式(在​​ 中使用[[ $a = pattern ]])是 shell(例如在 glob 中使用),而正则表达式则不然。

因此,在解析运算符的参数时,bash必须以不同的方式对待所有扩展的正则表达式运算符,这些运算符通常是特殊的 shell 字符,例如|, ()=~

不过,请注意,虽然

 [[ $a =~ (ab)*c ]]

现在有效,

 [[ $a =~ [)}] ]]

没有。你需要:

 [[ $a =~ [\)}] ]]
 [[ $a =~ [')'}] ]]

在以前的版本中,bash反斜杠会错误地匹配。这个问题已经解决了,但是

 [[ $a =~ [^]')'] ]]

不是例如,像应该那样匹配反斜杠。因为bash无法意识到 是)在括号内,所以转义)会导致匹配除,和[^]\)]之外的任何字符的正则表达式。]\)

ksh93在这方面有更严重的错误。

在 中zsh,它是一个正常的 shell 单词,并且引用正则表达式运算符不会影响正则表达式运算符的含义。

[[ $a =~ 'a|b' ]]

与正则表达式匹配a|b

这意味着=~也可以将其添加到[/test命令中:

[ "$a" '=~' 'a|b' ]
test "$a" '=~' 'a|b'

(也可以在 . 中使用yash。需要在as=~中引用,因为那里是一个特殊的 shell 运算符)。zsh=something

bash 3.1 过去的行为类似于zsh.它在 3.2 中发生了变化,大概是为了与 一致ksh93(尽管bash是第一个提出的 shell [[ =~ ]]),但您仍然可以执行BASH_COMPAT=31shopt -s compat31恢复到以前的行为(除了 while[[ $a =~ a|b ]]在 3.1 中会返回错误bash,它不再bash -O compat31与较新版本的) 一起使用bash

希望它能澄清为什么我说这些规则令人困惑以及为什么使用:

[[ $a =~ $var ]]

有助于包括向其他 shell 的可移植性。

答案2

标准 glob(“文件名扩展”)是:*?[ ... ].|在标准(非 extglob)设置中不是有效的 glob 运算符。

尝试:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched

答案3

如果你想要正则表达式匹配,测试将是:

[[ "$a" =~ a|b ]]

相关内容