为什么不按字面意义处理`|`?


13

我的问题来自如何将正则表达式存储在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 我以前的帖子|在解释之初确认为管道,(其中包括解析例子中的条件表达式)解释的任何其他步骤之前也。那么,如何|在使用时将其识别为regex运算符=~,而又在使用时如何将 其识别为无效使用的管道=?这让我认为第1部分中的语法错误并不意味着将|其解释为管道。

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

谢谢。


1
请注意,在bash的某些版本中,extglob解析(这里|是特殊的)是默认在右手边[[ $var = $pattern ]]。隔离shopt出现此行为的版本和选项配置会很有趣-如果只是extglob默认情况下或显式配置启用的版本和选项配置,那么就可以了。
Charles Duffy

2
顺便说一句,如果您想更全面地排除管道字符干扰解析前阶段的情况(我同意这是没有发生的,但是对读者来说可能不那么明显),使用pattern='a|b',然后$pattern在RHS上展开未引用的内容。
Charles Duffy

@CharlesDuffy,这是问答环节中提出的要点,这个问题是后续问题。
斯特凡Chazelas

啊-上下文很有意义;而且您的回答非常出色。两项都谢谢你。
Charles Duffy

蒂姆,请问以下任何答案都可以回答您的问题吗?如果可以,请考虑接受一个。谢谢!
杰夫·谢勒

Answers:


13

没有充分的理由

[[ $a = a|b ]]

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

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

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

在普通的shell命令行中:

echo a|b

echo a输送到ba|b不是WORD,而是三个令牌:a WORD|令牌和b WORD令牌。

在中使用时[[ $a = a|b ]]bash期望得到一个()的WORDa,但随后发现一个|导致错误的意外令牌。

有趣的是,bash不要抱怨:

[[ $a = a||b ]]

由于它现在是a一个||标记,后面是b,后面是,因此其解析方式与:

[[ $a = a || b ]]

正在测试的$aa或该b字符串为非空。

现在,在:

[[ $a =~ a|b ]]

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

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

a|bregexp 不再匹配,而是与a\|bregexp 匹配。也就是说,shell引用具有消除regexp运算符特殊含义的副作用。这是一项功能,因此其行为与之类似[[ $a = "?" ]],但是通配符模式(用于[[ $a = pattern ]])是shell WORDS(例如,用于glob),而正则表达式则不是。

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

不过,请注意

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

现在可以了,

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

没有。你需要:

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

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

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

难道不是像它应该例如匹配反斜线。由于bash未能意识到该)内容在方括号内,因此对)进行转义以[^]\)]生成与]\和上任何字符都匹配的正则表达式)

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

在中zsh,这是一个正常的shell字词,用引号regexp运算符不会影响regexp运算符的含义。

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

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

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

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

(也可以在中使用yash=~需要zsh作为=something特殊的shell运算符引用在其中)。

bash 3.1以前的表现就好zsh。它在3.2中进行了更改,大概是与对齐的ksh93(即使bash是第一个出现的shell [[ =~ ]]),但是您仍然可以这样做BASH_COMPAT=31shopt -s compat31还原为以前的行为(除了[[ $a =~ a|b ]]bash3.1中返回错误时,它不再存在了)的bash -O compat31新版本中bash)。

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

[[ $a =~ $var ]]

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


zsh还报告的错误[[ $a = a|b ]]
NotAnUnixNazi

@isaac,是的,这就是我在这里提出的重点。a|b是不是shell WORD这里,它是a|b令牌。就像echo a|b不输出a|b或不扩展a|b全局一样,您需要引用它,|因为它是一个特殊的外壳字符,在该上下文中无效。[[ $a = (a|b) ]]会像zsh通配符运算符一样echo (a|b)工作(a|b)
斯特凡Chazelas

您答案的措辞和解释仅称呼bash。那不是全部事实。
NotAnUnixNazi

11

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

尝试:

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

1
谢谢。但是为什么不|按字面意思理解呢?为什么会出现语法错误?
蒂姆(Tim)

1
没有引用。
杰夫·

3
在标准设置中,|不是glob运算符,因此不按|字面解释就不加引号吗?那么为什么会有语法错误?
蒂姆(Tim)

1
|是控制字符;它永远不会像字母或数字一样被当作文字字符。
chepner '17

3
因为在那种模式下,shell期望在尚未关闭的[[]]中间出现管道重定向字符。[[ $a = a不是有效的命令,其输出可以通过管道传递到另一个进程(至少这是Shell认为您正在尝试执行的操作)。
杰森·C

5

如果您想匹配正则表达式,则测试为:

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

@Tim您应该打开新问题,而不要连续编辑当前问题。
gardenhead

@gardenhead:我的更新是澄清我的问题,而不是更改它们,以防万一您错过了。我添加的第二部分是显示有关我原始问题(为什么语法错误)不正确的一个comment的管道解释。
蒂姆(Tim)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.