Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Bash script throws syntax errors when the 'extglob' option is set inside a subshell or function

Ask Question

Problem

The execution of a Bash script fails with the following error message when the 'extglob' option is set inside a subshell :

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

It fails in the same manner inside a function:

#!/usr/bin/env bash
set -euo pipefail
list_no_vendor () {
    shopt -s extglob
    for f in ?(.)!(|+(.)|vendor); do
        echo "$f"
list_no_vendor

Investigation

In both cases, the script executes successfully when the option is set globally, outside of the subshell or function.

Surprisingly, when set locally, the 'extglob' option appears to be effectively enabled in both the subshell and function:

#!/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

Output:

In the subshell: extglob            on
In the main shell: extglob          off
In the function: extglob            on

This makes the syntax error extremely puzzling to me.

Workaround

Passing a heredoc to the bash command works.

#!/usr/bin/env bash
set -euo pipefail
bash <<'EOF'
    shopt -s extglob
    echo 'In the child:' "$(shopt extglob)"
echo 'In the parent:' "$(shopt extglob)"

Output:

In the child: extglob           on
In the parent: extglob          off

However I would be curious to understand the gist of the problem here.

BTW, set -e is rather widely frowned on. See BashFAQ #105, skipping the allegory for the exercises below if in a hurry, and in-ulm.de/~mascheck/various/set-e, listing incompatibilities (of which there are many) amongst different shells' implementations of same. – Charles Duffy Mar 14, 2018 at 17:21 Beyond my rather terse answer -- I don't understand what the surprise is here. Could you be more clear about how your mental model and actual behavior conflict? (That flags are inherited by subshells should be entirely unsurprising, whether or not they were enabled within a function context; that flags set in a function are not cleared on leaving it, likewise, doesn't contradict any documentation I'm aware of, and thus should likewise be expected). – Charles Duffy Mar 14, 2018 at 17:24 I understand that flags are inherited. I am surprised because I was expecting my syntax to be valid in the context of my subshell (and invalid outside of it). – Antoine Cotten Mar 14, 2018 at 17:31

extglob is a flag used by the parser. Functions, compound commands, &c. are parsed in entirety ahead of execution. Thus, extglob must be set before that content is parsed; setting it at execution time but after parse time does not have any effect for previously-parsed content.

This is also why you can't run shopt -s extglob; ls !(*.txt) as a one-liner (when extglob is previously unset), but must have a newline between the two commands.

Not as an example of acceptable practice, but as an example demonstrating the behavior, consider the following:

#!/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"

No such error takes place here, because parsing of the content passed to eval happens only after the shopt -s extglob was executed, rather than when the block of code to be run in a subshell is parsed.

I'll add for clarify that shopt -s extglob needs to run before the current context, not just the current line. I had a case where my need for extglob was inside an 'if' and so shopt -s extglob had to be run outside the 'if'. Placing it above the needed line was not enough (received the same error "...unexpected token `('". – codesniffer Dec 5, 2018 at 15:26

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.