在复合命令中设置bash选项


9

我发现extglob在复合化合物中设置shell选项会导致后续的抗glob失败。是否需要在复合命令之外设置外壳程序选项?在bash手册页中没有看到这样要求的指示。

例如,以下脚本可以正常工作(打印a.0 a.1):

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
shopt -s extglob
ls "a."!(b*)

但是,如果最后两行作为复合命令执行,则脚本将失败,并出现以下错误:

syntax error near unexpected token `('
`    ls "a."!(b*)'

使用4.2到4.4的bash版本以及各种复合命令对此进行了测试:

(1)有条件的- if

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
if true; then
    shopt -s extglob
    ls "a."!(b*)
fi

(2)大括号- { }

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
{
    shopt -s extglob
    ls "a."!(b*)
}

(3)subshel​​l- ( )

#!/bin/bash
touch a.0   a.1 \
      a.b.0 a.b.1
(
    shopt -s extglob
    ls "a."!(b*)
)

在所有情况下,如果将shopt移到复合命令之外,则脚本将成功。

Answers:


14

在解析整个命令之前,复合命令的任何部分都不会执行,这在该手册的“ 外壳操作”部分中有详细介绍:所有标记和解析都在“ the”命令执行之前进行。启用extglob通过添加新的模式匹配运算符来更改语言语法,这些运算符在标记化过程中可以识别。

由于shopt尚未!(到达命令,因此该命令被视为已尝试的历史记录扩展(!)或控制运算符(,而不是!(被视为单个项目(与@(,等等一样),只是没有历史记录扩展那里)。

当解析到达该令牌时,这两种情况通常都会出错,尽管!(...)在行的开头或之后time是否定的subshel​​l管道。“ unexpected token `('”表示该点不能接受subshel​​l表达式。

当您只想在子shell中临时启用extglob时,这有点烦人。一种解决方法是定义一个使用所需glob的函数,然后重新禁用extglob,然后从启用了extglob的子外壳中调用该函数:

shopt -s extglob
f() { ls "a."!(b*) ; }
shopt -u extglob
(
    shopt -s extglob
    f
)

非常笨拙。


如果在复合命令中创建别名,则会发生相同的效果,因为在解析之前也会对它们进行处理:

if true
then
    alias foo=echo
    foo bar
fi
foo xyz

将先打印foo: command not found,然后再打印,xyz因为别名创建,但在if完成后才可用。


谢谢-我没有意识到复合命令在执行之前就被解析为一个单元。该行为现在很有意义。
user001

3
@ user001我不会那么仁慈地说这有意义。切换词法分析的模式,而在解析什么闻所未闻的,和其他语言一样C或者perl是能够做到这一点的罚款。请注意,仅作为复合命令的一部分是不够的,shopt -s extglob还必须在单独的行上。另请参阅此处
mosvy

@mosvy:我的意思是,一旦人们意识到bash将一个复合命令解析为一个单元(不是解析器的限制一定是合理的),观察到的行为就有意义。感谢您链接到另一篇文章中的讨论。
user001
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.