7

According to the bash manual, if extglob is enabled, the pattern @(pattern-list) should match any of the patterns in pattern-list (separated by |). Here it works as expected:

$ shopt -s extglob
$ ls -ld /@(.|usr)/@(.|local)/@(.|share)/
drwxr-xr-x  50 root root   4096 Sep  2 16:39 /./././
drwxr-xr-x  12 root root   4096 Oct 15  2018 /usr/././
drwxrwsr-x  10 root staff  4096 Oct 15  2018 /usr/local/./
drwxrwsr-x  10 root staff  4096 Oct 15  2018 /usr/local/share/
drwxr-xr-x 725 root root  20480 Sep  2 16:42 /usr/./share/

But if we swap the alternatives in each of the three pattern lists, most of the directories that should have been matched are gone:

$ ls -ld /@(usr|.)/@(local|.)/@(share|.)/
drwxrwsr-x 10 root staff 4096 Oct 15  2018 /usr/local/share/

The same with a non-existing subdirectory. Here it works:

$ ls -ld /@(.|usr)/@(.|foo)/@(.|share)/
drwxr-xr-x  50 root root  4096 Sep  2 16:39 /./././
drwxr-xr-x  12 root root  4096 Oct 15  2018 /usr/././
drwxr-xr-x 725 root root 20480 Sep  2 16:42 /usr/./share/

And here it doesn't:

$ ls -ld /@(usr|.)/@(foo|.)/@(share|.)/
ls: cannot access '/@(usr|.)/@(foo|.)/@(share|.)/': No such file or directory

What's going on here? Is this behavior documented somewhere, or is it just plain buggy? (This is GNU bash, version 4.4.12(1).)

Uwe
  • 3,297
  • 18
  • 19
  • The last error will go away if you shopt -s nullglob, or use shopt -s failglob for a "better" error. – glenn jackman Oct 25 '19 at 19:39
  • This is a really interesting question. The first pattern acting halfway between the 2nd and the brace expanding /{.,usr}/{.,local}/{.,share}/ is really weird. – glenn jackman Oct 25 '19 at 19:43
  • Further data points: bash version 5 acts this same way. For bash version 3.2, the 1st pattern acts the same as the 2nd. Something changed in version 4.x -- if you're feeling intrepid, wade through https://git.savannah.gnu.org/cgit/bash.git/tree/CHANGES – glenn jackman Oct 25 '19 at 19:45
  • @glennjackman The exact output depends of course on which directories are present in the filesystem. For me, the only difference between the first pattern and your brace pattern is that the latter expands also to non-existing directories such as /././share/, resulting in an error message by ls. But that difference is to be expected; it's the same difference as between "echo /[uv]sr" and "echo /{u,v}sr". – Uwe Oct 25 '19 at 20:46
  • Yes agreed. That's because brace expansion is not filename expansion -- brace expansion just generates strings. – glenn jackman Oct 25 '19 at 21:03

1 Answers1

2

Prior to bash-4.3, the "." term would never match. From bash(1), v5.0, section Pathname Expansion:

                                                When a  pattern  is  used
  for  pathname expansion, the character ``.''  at the start of a name or
  immediately following a slash must be matched  explicitly,  unless  the
  shell  option  dotglob  is  set.  The filenames ``.''  and ``..''  must
  always be matched explicitly, even if dotglob is set.

The description of the behaviour here is a little vague, but it doesn't mean that "." must be at the start of each (sub-)pattern, you can prove that with:

$ echo  /@(usr|.)/@(local|.)/@(share|.)/
/usr/local/share/
$ echo  /@(usr|..)/@(local|..)/@(share|..)/
/../../../ /usr/../../ /usr/local/../ /usr/local/share/

So the problem is specific to "." and not "..".

I believe this is a bug in extglob_skipname(), starting at line 218 in the while (t = glob_patscan (pp, pe, '|')) { ... } loop, the final term in such a pattern is not handled properly (interaction with the leading "." supression logic in skipname()) so "." never matches but ".." manages to match. (glob_patscan is aka PATSCAN thanks to macro games.)

Either of these work too:

$ echo  /@(usr|.|)/@(local|.|)/@(share|.|)/
/./././ /usr/././ /usr/./share/ /usr/local/./ /usr/local/share/
$ echo  /@(usr|.|.)/@(local|.|.)/@(share|.|.)/
/./././ /usr/././ /usr/./share/ /usr/local/./ /usr/local/share/

So the answer is the sub-pattern order shouldn't matter, and doesn't matter, but seemingly a bug causes problems when the final term is a ".".

mr.spuratic
  • 9,901