在子shell或函数内设置'\ textglob'选项时,Bash脚本会引发语法错误

Ant*_*ten 3 bash

问题

在子shell中设置'extglob'选项时,Bash脚本的执行失败并显示以下错误消息:

/tmp/foo.sh: line 7: syntax error near unexpected token `('
Run Code Online (Sandbox Code Playgroud)
#!/usr/bin/env bash
set -euo pipefail
(
    shopt -s extglob
    for f in ?(.)!(|+(.)|vendor); do
        echo "$f"
    done
)
Run Code Online (Sandbox Code Playgroud)

它在函数内以相同的方式失败:

#!/usr/bin/env bash
set -euo pipefail

list_no_vendor () {
    shopt -s extglob
    for f in ?(.)!(|+(.)|vendor); do
        echo "$f"
    done
}

list_no_vendor
Run Code Online (Sandbox Code Playgroud)

调查

在这两种情况下,当在子shell或函数之外全局设置选项时,脚本会成功执行.

令人惊讶的是,在本地设置时,'extglob'选项似乎在子shell和函数中有效启用:

#!/usr/bin/env bash
set -euo pipefail

(
    shopt -s extglob
    echo 'In the subshell:' "$(shopt extglob)"
)

list_no_vendor () {
    shopt -s extglob
    echo 'In the function:' "$(shopt extglob)"
}

echo 'In the main shell:' "$(shopt extglob)"

list_no_vendor
Run Code Online (Sandbox Code Playgroud)

输出:

In the subshell: extglob            on
In the main shell: extglob          off
In the function: extglob            on
Run Code Online (Sandbox Code Playgroud)

这使得语法错误对我来说非常令人费解.

解决方法

将heredoc传递给bash命令可以正常工作.

#!/usr/bin/env bash
set -euo pipefail

bash <<'EOF'
    shopt -s extglob
    echo 'In the child:' "$(shopt extglob)"
EOF

echo 'In the parent:' "$(shopt extglob)"
Run Code Online (Sandbox Code Playgroud)

输出:

In the child: extglob           on
In the parent: extglob          off
Run Code Online (Sandbox Code Playgroud)

但是我很想知道这里问题的要点.

Cha*_*ffy 5

extglob是解析器使用的标志.函数,复合命令和c.在执行之前完整地解析.因此,extglob必须在解析内容之前设置; 在执行时设置它但在解析时间之后对先前解析的内容没有任何影响.

这也是为什么你不能shopt -s extglob; ls !(*.txt)作为单行运行(extglob以前未设置),但必须在两个命令之间有换行符的原因.


不作为可接受的实践的示例,但作为演示行为的示例,请考虑以下内容:

#!/usr/bin/env bash
(
    shopt -s extglob
    # Parse of eval'd code is deferred, so this succeeds
    eval '
        for f in ?(.)!(|+(.)|vendor); do
            echo "$f"
        done
    '
)
Run Code Online (Sandbox Code Playgroud)

没有这样的错误发生在这里,因为传递到内容的解析eval只发生后的shopt -s extglob被执行,而不是当一个子shell解析要运行的代码块.

  • 我将补充说明 shopt -s extglob 需要在当前上下文之前运行,而不仅仅是当前行。我有一个案例,我对 extglob 的需求在“if”内,因此 shopt -s extglob 必须在“if”之外运行。将它放在所需的行上方是不够的(收到相同的错误“...意外标记`('”。 (4认同)