9

I have found that setting the extglob shell option within a compound compound results in failure of subsequent anti-globs. Are shell options required to be set outside of compound commands? I did not see an indication of such a requirement in the bash man pages.

As an example, the following script works fine (prints a.0 a.1):

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

However, if the last two lines are executed as a compound command, the script fails with the following error:

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

This was tested using bash versions from 4.2 to 4.4 and with a variety of compound commands:

(1) conditional -- 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) braces -- { }

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

(3) subshell -- ( ):

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

In all cases, if the shopt is moved outside the compound command, the script succeeds.

14

No part of a compound command executes before the entire command is parsed, which is obliquely documented in the Shell Operation section of the manual: all tokenising and parsing happens before "the" command executes. Enabling extglob changes the language syntax by adding new pattern-matching operators, which are recognised during tokenisation.

Because the shopt command hasn't executed at the time !( is reached, it's seen as either an attempted history expansion (!) or the control operator (, rather than !( being seen as a single item (and the same for @(, etc, except that there's no history expansion there).

When parsing reaches that token either case will generally be an error, though !(...) at the start of a line or after time is a negated subshell pipeline. "unexpected token `('" means it couldn't accept a subshell expression in that spot.

This is somewhat annoying when you want extglob enabled only temporarily in a subshell. One workaround is to define a function that uses the glob you want, then re-disable extglob, and then call the function from the subshell with extglob enabled:

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

It's very clunky.


The same effect happens if you create an alias within a compound command, because they are also processed before parsing:

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

will print foo: command not found, and then xyz, because the alias is created, but not available until the if is done.

3
  • Thanks -- I didn't realize that compound commands were parsed as a unit before execution. The behavior now makes sense.
    – user001
    Jun 2 '19 at 23:00
  • 3
    @user001 I wouldn't be so charitable to say that it makes sense. Switching the lexer's mode while in parsing is nothing unheard of, and other languages like C or perl are able to do it fine. Notice that not being part of a compound command isn't enough, shopt -s extglob also has to be on a separate line. See also the discussion and examples here
    – mosvy
    Jun 3 '19 at 0:43
  • @mosvy: I meant that the observed behavior makes sense once one realizes that bash parses a compound command as a unit (not that the limitations of the parser are necessarily reasonable). Thanks for linking to the discussion in the other post.
    – user001
    Jun 3 '19 at 3:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.