1

Short Version:

When extended glob is turned on inside a bash if block on my Mac, the entire block fails with a syntax error on any extend glob pattern contained in the block, even if it follows the activation. It's not a problem on my other machines. Why?

Long Version:

I put the following if condition around the entirety of my .bashrc file. Similar if statements have been put around my other .* files that are run during a shell's initialisation. I'm doing this to prevent recursive executions on the file if I make an error in the future.

if [ -n "${_BASHRC_INIT}" ] ; then
export _BASHRC_INIT="True"

... ... ...

unset _BASHRC_INIT fi

I have this function, laa, defined in my .bashrc that depends on extended glob. I enable it immediately before I define the function. The function shows all the files in a directory that match .* excluding . and ... Since I have ls aliased to ls --color=auto, it also shows file-type colours, which wouldn't be maintained if I just piped to grep. It works when not inside an if block.

shopt -s extglob
laa() {
  if [[ $# -eq 1 ]]; then
    exec 3>&1                                 # Another file descriptor for this STDOUT
    _items=$(cd "${1}"; ls -d .!(|.)?* 1>&3)  # This requires extended glob
                                              # `shopt -s extglob` to enable
    exec 3>&-                                 # Close file descriptor
  elif [ $# -gt 1 ]; then
    exec 3>&1
    for i in $@; do
      _items=$(cd $i; echo "$i:" 1>&3; ls -d .!(|.)* 1>&3)
    done
    exec 3>&-
  else
    ls -d .!(|.)*
  fi
}

All the commands contained in the if block fail to execute on my M1 Mac when the block also contains this excerpt. This problem does not occur with WSL2 (Debian) on my Windows PC. The Mac is running bash 5.2.15 installed with Homebrew and WSL runs bash 5.1.4.

Specifically, it fails on the ls commands with the following error.

bash: syntax error near unexpected token `('
bash: `    _items=$( cd "${1}"; ls -d .!(|.)?* 1>&3 )  # This requires extended glob'

The entire if block executes as expected if I execute shopt -t extglob before entering it. This is my current solution. However, this breaks the convention I'm trying to set across my environments. I'm trying to use the same .* files across my machines, which also helps with quickly creating a consistent environment on a new machine.

What is causing the extra syntax check for the contents of the if block?

  • This happens because bash needs to parse the entire if ... fi block (including that glob expression) before it can begin executing it, and therefore extglob needs to be set before the beginning of the if block. The same thing applies to using extended globs inside any complex command or even function definition. See my answer to "Case statement with extglob" on stackexchange. – Gordon Davisson Dec 26 '22 at 19:13

1 Answers1

0

You can likely avoid KSH-style extended globs here altogether - see for example how to glob every hidden file except current and parent directory.


Under compat51 in 6.12 Shell Compatibility Mode

Parsing command substitutions will behave as if extended glob (see The Shopt Builtin) is enabled, so that parsing a command substitution containing an extglob pattern (say, as part of a shell function) will not fail. This assumes the intent is to enable extglob before the command is executed and word expansions are performed. It will fail at word expansion time if extglob hasn’t been enabled by the time the command is executed.

So consider for example

#!/bin/bash

echo "$BASH_VERSION"

laa() { if [[ $# -eq 1 ]]; then exec 3>&1 # Another file descriptor for this STDOUT _items=$(cd "${1}"; ls -d .!(|.)?* 1>&3) # This requires extended glob # shopt -s extglob to enable exec 3>&- # Close file descriptor elif [ $# -gt 1 ]; then exec 3>&1 for i in $@; do _items=$(cd $i; echo "$i:" 1>&3; ls -d .!(|.)* 1>&3) done exec 3>&- fi }

shopt -s extglob laa .

where extglob is set after function definition but before execution (as it would be if the function were sourced from your .bashrc file) then whereas under bash 5.1 it works:

$ /bin/bash ./myscript
5.1.16(1)-release
.admins        .cache     .hardinfo  .parallel        .RData            .ssh                       .vboxclient-display-svga-x11.pid  .Xauthority       .zcompdump
.bash_history  .config    .lesshst   .password-store  .Rhistory         .sudo_as_admin_successful  .vboxclient-draganddrop.pid       .xinitrc          .zsh_history
.bash_logout   .dialogrc  .local     .profile         .selected_editor  .test_dot_folder           .vboxclient-seamless.pid          .xscreensaver     .zshrc
.bashrc        .gnupg     .mozilla   .python_history  .spectrum3d       .vboxclient-clipboard.pid  .wget-hsts                        .xsession-errors

under 5.2 it doesn't:

$ ./src/bash-5.2.15/bash ./myscript
5.2.15(1)-release
./myscript: line 8: syntax error near unexpected token `('
./myscript: line 8: `    _items=$(cd "${1}"; ls -d .!(|.)?* 1>&3)  # This requires extended glob'

However, the behavior outside of a command substitution is the same; so once you add the else block:

#!/bin/bash

echo "$BASH_VERSION"

laa() { if [[ $# -eq 1 ]]; then exec 3>&1 # Another file descriptor for this STDOUT _items=$(cd "${1}"; ls -d .!(|.)?* 1>&3) # This requires extended glob # shopt -s extglob to enable exec 3>&- # Close file descriptor elif [ $# -gt 1 ]; then exec 3>&1 for i in $@; do _items=$(cd $i; echo "$i:" 1>&3; ls -d .!(|.)* 1>&3) done exec 3>&- else ls -d .!(|.)* fi }

shopt -s extglob laa .

your code should fail in both cases - just sooner under 5.2

$ ./src/bash-5.2.15/bash ./myscript
5.2.15(1)-release
./myscript: line 8: syntax error near unexpected token `('
./myscript: line 8: `    _items=$(cd "${1}"; ls -d .!(|.)?* 1>&3)  # This requires extended glob'
$ 
$ /bin/bash ./myscript
5.1.16(1)-release
./myscript: line 18: syntax error near unexpected token `('
./myscript: line 18: `    ls -d .!(|.)*'

If you must rely on the bash 5.1 behavior, then you can set BASH_COMPAT=51 before the function definition in your .bashrc file.

steeldriver
  • 81,074