在复合命令中设置 bash 选项

在复合命令中设置 bash 选项

我发现extglob仅在复合复合中设置 shell 选项会导致后续扩展 glob 失败。是否需要在复合命令之外设置 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*)

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

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) 子外壳 -- ( ):

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

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

答案1

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

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

当解析到达该标记时,任何一种情况通常都会出错,尽管!(...)在行的开头或之后time是一个否定的子 shell 管道。 “ unexpected token `('”表示它无法在该位置接受子 shell 表达式。

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

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创建,但在完成之前不可用。

相关内容