在复合命令中设置 bash 选项

use*_*001 9 bash shopt

我发现extglob在复合化合物中设置shell 选项会导致随后的 anti-globs 失败。是否需要在复合命令之外设置 shell 选项?我在 bash 手册页中没有看到此类要求的指示。

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

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

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

syntax error near unexpected token `('
`    ls "a."!(b*)'
Run Code Online (Sandbox Code Playgroud)

这是使用从 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
Run Code Online (Sandbox Code Playgroud)

(2) 大括号—— { }

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

(3) 子外壳—— ( )

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

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

Mic*_*mer 14

在解析整个命令之前,复合命令的任何部分都不会执行,这在手册的Shell 操作部分有斜记载:所有标记和解析都发生在“the”命令执行之前。启用extglob通过添加新的模式匹配运算符来更改语言语法,这些运算符在标记化期间会被识别。

因为shopt命令在到达时尚未执行!(,所以它被视为尝试的历史扩展 ( !) 或控制运算符(,而不是!(被视为单个项目(与@(等相同,只是没有历史扩展那里)。

当解析到达该标记时,任何一种情况通常都会是一个错误,尽管!(...)在一行的开头或之后time是一个否定的 subshel​​l 管道。" unexpected token `('" 表示它不能接受该位置的子外壳表达式。

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

shopt -s extglob
f() { ls "a."!(b*) ; }
shopt -u extglob
(
    shopt -s extglob
    f
)
Run Code Online (Sandbox Code Playgroud)

它非常笨重。


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

if true
then
    alias foo=echo
    foo bar
fi
foo xyz
Run Code Online (Sandbox Code Playgroud)

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

  • @user001 我不会这么仁慈地说这是有道理的。切换词法分析器的模式*在解析时* 是闻所未闻的,其他语言如`C` 或`perl` 能够很好地做到这一点。请注意,不是复合命令的一部分是不够的,`shopt -s extglob` 还必须在单独的行上。另请参阅[此处]的讨论和示例(https://unix.stackexchange.com/questions/521287/file-globbing-pattern-example-behaves-differently-in-bash-script-than-it-d/521289#评论964577_521289) (3认同)