为什么不以 glob 模式逐字处理 `|`?

Tim*_*Tim 13 bash

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

  1. 为什么会出现错误:

    $ [[ $a = a|b ]]  
    bash: syntax error in conditional expression: unexpected token `|'
    bash: syntax error near `|b'
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

    然后将=全局模式更改=~为正则表达式模式使 |工作

    $ [[ $a =~ a|b ]]
    
    Run Code Online (Sandbox Code Playgroud)

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

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

谢谢。

Sté*_*las 14

没有充分的理由为什么

[[ $a = a|b ]]
Run Code Online (Sandbox Code Playgroud)

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

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

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

在普通的 shell 命令行中:

echo a|b
Run Code Online (Sandbox Code Playgroud)

echo a输送到b. a|b不是WORD,它是三个标记:a WORD|标记和b WORD标记。

当在 中使用时[[ $a = a|b ]],它bash需要一个WORD,它得到 ( a),但随后发现了一个意外的|标记,这导致了错误。

有趣的是,bash不会抱怨:

[[ $a = a||b ]]
Run Code Online (Sandbox Code Playgroud)

因为它现在是一个a令牌后跟一个||令牌后跟b,所以它的解析方式与:

[[ $a = a || b ]]
Run Code Online (Sandbox Code Playgroud)

这是测试一个$aa或该b字符串非空。

现在,在:

[[ $a =~ a|b ]]
Run Code Online (Sandbox Code Playgroud)

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

[[ $a =~ 'a|b' ]]
Run Code Online (Sandbox Code Playgroud)

这不再与正则a|b表达式匹配,而是与正则a\|b表达式匹配。也就是说,shell 引用具有去除正则表达式运算符的特殊含义的副作用。这是一个特性,所以行为与那个类似[[ $a = "?" ]],但通配符模式(在​​ 中使用[[ $a = pattern ]])是 shell WORDS(例如在 globs 中使用),而正则表达式不是。

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

不过,请注意,虽然

 [[ $a =~ (ab)*c ]]
Run Code Online (Sandbox Code Playgroud)

现在工作,

 [[ $a =~ [)}] ]]
Run Code Online (Sandbox Code Playgroud)

没有。你需要:

 [[ $a =~ [\)}] ]]
 [[ $a =~ [')'}] ]]
Run Code Online (Sandbox Code Playgroud)

在以前的版本中bash会错误地匹配反斜杠。那个是固定的,但是

 [[ $a =~ [^]')'] ]]
Run Code Online (Sandbox Code Playgroud)

难道不是像它应该例如匹配反斜线。因为bash没有意识到它)在方括号内,所以转义 the)以产生一个[^]\)]匹配任何字符的正则表达式,但], \and )

ksh93 在这方面有更糟糕的错误。

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

[[ $a =~ 'a|b' ]]
Run Code Online (Sandbox Code Playgroud)

与正则a|b表达式匹配。

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

[ "$a" '=~' 'a|b' ]
test "$a" '=~' 'a|b'
Run Code Online (Sandbox Code Playgroud)

(也适用于yash.=~需要引用zshas=something是那里的特殊 shell 运算符)。

bash 3.1 过去表现得像zsh. 它在3.2改变了,大概要对齐ksh93(即使bash是外壳,首先想出了[[ =~ ]]),但你仍然可以做BASH_COMPAT=31shopt -s compat31恢复到以前的行为(不同的是,同时[[ $a =~ a|b ]]将在返回一个错误bash3.1,现在不在bash -O compat31较新版本的bash) 中。

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

[[ $a =~ $var ]]
Run Code Online (Sandbox Code Playgroud)

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


Jef*_*ler 11

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

尝试:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched
Run Code Online (Sandbox Code Playgroud)

  • 在标准设置中,`|` 不是全局运算符,所以`|` 不是不加引号就按字面解释的吗?那么为什么会出现语法错误呢? (3认同)
  • 因为在那种模式下,shell 不希望在尚未关闭的 [[]] 中间出现管道重定向字符。`[[ $a = a` 不是一个有效的命令,它的输出可以通过管道传输到另一个进程(至少 shell 认为你试图这样做)。 (3认同)

Dea*_*rip 5

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

[[ "$a" =~ a|b ]]
Run Code Online (Sandbox Code Playgroud)