~ glob operator behavior with empty pattern

wildcardszsh

Per the documentation, ~ can be used as glob operator when EXTENDED_GLOB is set, allowing constructs like

print -rl glob*~globA*~glob*B

so anything that matches glob* but does not match globA* and glob*B.
All right:

touch file{1,2,3,4{,5}}
setopt extendedglob

now

print -rl file*~f*3~file4* 

behaves as expected:

file1
file2

but when an empty pattern is used

print -rl file*~f*3~~file4*

prints

file1
file2
file3

It looks like only the last pattern is used to filter the results of the 1st glob (2nd and 3rd – which is empty – are ignored).

print -rl file*~f*3~file4*~~

produces

file1
file2
file4
file45

so now only the 2nd one is used…
What's weird is that if the pattern after the 1st tilde is empty it does not work at all e.g.

print -rl file*~~f*3~file4*

errors out with

zsh: no matches found: file*~~f*3~file4*

What am I missing here ?


this is with zsh 5.3.1 if it matters…

Best Answer

~~ doesn't use an empty pattern. Instead, it does one of two things:

  • At the end of the whole pattern, literally matches "~~".
  • In the middle, literally matches a single "~", then moves on to the next excluded pattern (i.e. a literal ~ followed by the ~ operator).

If we extend your file set with a few extras we can see that happen:

touch file3~ file3~~ file4~ file4~~

Now running your first odd command:

print -rl file*~f*3~~file4*
file1
file2
file3
file3~~

file3~ has been excluded, as have all three file4*s. file3~~ is still there, because it doesn't match f*3~.

The second one:

print -rl file*~f*3~file4*~~
file1
file2
file3~
file3~~
file4
file45
file4~

Only file4~~ has been excluded, because that's the only one matching file4*~~.

The final case now does have output:

print -rl file*~~f*3~file4*
file3~
file3~~

since those are the two files matching file*~ that don't match either f*3 or file4*.

Perhaps this could be a parsing bug, at least for the literal-then-operator case, but I can't see a use for an empty exclusion pattern so I'm not sure what else it should do.