Zsh Recursive Glob – How to Exclude a Directory Name

recursivewildcardszsh

I'm running zsh on Linux under setopt extended_glob ksh_glob glob_dots. I'm looking for something easy to type on the command line, with no portability requirements. I'm looking at a source code tree, with no “weird” file names (e.g. no \ in file names, no file name beginning with -).

Either of the following commands print the list of subdirectories of the current directory recursively:

find -type d
print -l **/*/

This is actually an svn checkout:

$ find -type d
./.deps
./.svn
./.svn/text-base
./.svn/prop-base
./.svn/tmp
./.svn/tmp/text-base
./.svn/tmp/prop-base
./.svn/tmp/props
./.svn/props
./lib/.svn
./lib/.svn/text-base
./lib/.svn/prop-base
./lib/.svn/tmp
./lib/.svn/tmp/text-base
./lib/.svn/tmp/prop-base
./lib/.svn/tmp/props
./src/.svn
./src/.svn/text-base
./src/.svn/prop-base
./src/.svn/tmp
./src/.svn/tmp/text-base
./src/.svn/tmp/prop-base
./src/.svn/tmp/props

I want to exclude the .svn directories and their subdirectories which are present in every directory. It's easy with find:

find -type d -name .svn -prune -o -print

Can I do this with a short zsh glob? Ignoring dot files comes close (I need to do it explicitly because I have glob_dots set):

print -l **/*(/^D)

But this isn't satisfactory because it hides the .deps directory, which I do want to see. I can filter out the paths containing .svn:

print -l **/*~(*/|).svn(|/*)(/)

But that's barely shorter than find (so what am I using zsh for?). I can shorten it to print -l **/*~*.svn*(/), but that also filters out directories called hello.svn. Furthermore, zsh traverses the .svn directories, which is a bit slow on NFS or Cygwin.

Is there a convenient (as in easy to type) way to exclude a specific directory name (or even better: an arbitrary pattern) in a recursive glob?

Best Answer

Zsh's extended glob operators support matching over / (unlike ksh's, even in zsh's implementation). Zsh's **/ is a shortcut for (*/)# (*/ repeated 0 or more times). So all I need to do is replace that * by ^.svn (anything but .svn).

print -l (^.svn/)#

Neat!