Note: Please don't respond about this being a XY situation. I am trying to understand how a globbing pattern works, not trying to achieve a specific result. Am also not interested in other ways to achieve this with zstyle, etc.

Recently I was in a situation where I wanted to complete only symbolic links from a specific directory as a function's argument. A pattern like *(@) works as an argument to -g of _files as long as the directory has at least one symbolic link. If there are no symbolic links in the directory, the completion filter fails as it now completes regular files (non symbolic links)... So I went through the documentation, and it seemed adding N (NULL GLOB) qualifier is the answer. I changed the pattern to *(@N), but that still returns regular files when there are no symbolic links. Surprisingly, adding a negation in front of N (pattern *(@^N)) makes it work correctly under all cases. But am a little unsure if this is because I hit a zsh bug (and this pattern will stop working in future) or if this pattern really makes sense and is worth relying on.

It would be great if someone would be able to confirm that the pattern below works not because its a zsh bug, and what it means. Here's a test file foocomp.zsh:

function foo {
    echo "foo called with $1"

function _foo {
    _files -g '*(@^N)' -W $PWD/foodir/

function bar {
    echo "bar called with $1"

function _bar {
    _files -g '*(@N)' -W $PWD/foodir/

compdef _foo foo

compdef _bar bar


  1. Save foocomp.zsh file to some directory
  2. mkdir foodir && cd foodir
  3. touch hello && ln -s hello world && cd ..
  4. source foocomp.zsh
  5. Type foo or bar and press tab, it should only complete world.
  6. Delete symbolic link world
  7. Type foo and press tab again, it would not complete anything (which is desired behavior)
  8. Type bar and press tab again, it will complete hello.

1 Answer 1


The _files function is a wrapper around _path_files and the _path_files code involves not just a glob but various other logic and zstyle settings; adding a -g glob influences what the pats array contains, sets a gopts flag that hits up various branches, and there is additional code that inspects whether elements in pats contain a glob qualifier, or not. You would really need to trace this function call to see what is going on for varied arguments, and how glob qualifiers influence the following code.

% less ${^fpath}/_path_files(N)

A global zstyle may interfere with the _path_files code in unexpected and hard to debug ways. For example you could set the *(@^N) glob for the foo command, and then some months later you or some random plugin makes a conflicting zstyle change, and then some months later you notice that the *(@^N) no longer operates as expected and have no clue why. With tracing we can see what is going on and test whether my informal reasoning is true:

% functions -t _path_files

Actually that's pretty annoying; _complete_debug will instead put the completion trace into a temporary file for you (control+x ?):

% bindkey | grep _debug
"^X?" _complete_debug

So we have four cases, foo and bar with and without a link present in foodir. So after some key pressing...

% mv zsh3827foo2 withlink-foo
% mv zsh3827bar3 withlink-bar
% mv zsh3827foo4 nolink-foo
% mv zsh3827bar5 nolink-bar

These might actually be easier to tell apart if the completion is modified between _complete_debug calls so that the command name differences do not complicate matters. Anyways, a notable difference, eventually, is

-       +_path_files:468> compfiles -p tmp1 accex '' ' ' .. fake '*(#q@^N)'
-       +_path_files:472> tmp1=
+       +_path_files:468> compfiles -p tmp1 accex '' ' ' .. fake '*(#q@N)'
+       +_path_files:472> tmp1=( )

that ^ for the correct foo sets tmp1= while the incorrect bar sets an empty array tmp1=( ), and then takes a long branch elsewhere before rejoining the shared completion track. So this is a fairly subtle point, and the usual error message is swallowed somewhere within compfiles; on the command line it can be seen that ^@ merely turns off NULL_GLOB in a directory with no symlinks present:

% a=( *(#q@^N) )
zsh: no matches found: *(#q@^N)
% a=( *(#q@N) )

Over in Src/Zle/computil.c the compfiles function is documented to return an array for parnam1:

 *     1. compfiles -p  parnam1 parnam2 skipped matcher sdirs parnam3 varargs [.
 *     1. Set parnam1 to an array of patterns....
 *        ${(P)parnam1} is an in/out parameter.

So I guess this might be a bug if compfiles is always supposed to return an array? But that's probably something that should be hashed out with the ZSH developers. Otherwise for this case I would still use the zstyle solution to avoid global zstyle changes from potentially affecting this completion code.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .