I am trying to do a file name generation of all objects (files, directories, and so on) recursively under all subdirectories of the current directory. Excluding the objects in said current directory.
In other words, given:
--dir1 --dir2.1
| | dir2.2 --file3.1
| --file2.1
--file1
I want to generate:
./dir2.1
./dir2.2
./dir2.2/file3.1
./file2.1
I have set the EXTENDED_GLOB option, and I assumed that the following pattern would do the trick:
./**/*~./*
But it returns:
zsh: no matches found: ./**/*~./*
I don't know what the problem is, it should work.
./**/* gives:
./dir1
./dir2.1
./dir2.2
./dir2.2/file3.1
./file2.1
./file1
And ./* gives:
./dir1
./file1
How come ./**/*~./* fails? And more important, how can I generate the name of the elements recursively in the subdirectories excluding the elements in current/base directory?
Thanks.
The (1)x~y
glob operator uses y
as a shell's ordinally pattern matching rather than a file name generation, so ./**/*~./*
gives "no matches found":
% print -l ./**/*~./*
;# ./dir1 # <= './*' matches, so exclude this entry
;# ./dir2.1 # <= './*' matches, so exclude this entry
;# .. # ditto...
;# => finally, no matches found
The exclusion pattern ./*
matches everything generated by the glob ./**/*
, so zsh finally yields "no matches found". (zsh does not do filename generations for the ~y
part.)
We could make the exclusion pattern a little more precise/complicated form for excluding the elements in current directory. Such that it starts with ./
and has one or more characters other than /
.
% print -l ./**/*~./[^/]## ;# use '~./[^/]##' rather than '~./*'
./dir1/dir2.1
./dir1/dir2.2
./dir1/dir2.2/file3.1
./dir1/file2.1
Then, to strip the current-dir-component /dir1
, we could use the (2)e
string glob qualifier, such that it removes the first occurrence of /[^/]##
(for example /dir1
):
# $p for avoiding repetitive use of the exclusion pattern.
% p='./[^/]##'; print -l ./**/*~${~p}(e:'REPLY=${REPLY/${~p[2,-1]}}':)
./dir2.1
./dir2.2
./dir2.2/file3.1
./file2.1
Or to strip it using ordinally array/replace rather than e
string glob qualifier:
% p='./[^/]##'; a=(./**/*~${~p}) ; a=(${a/${~p[2,-1]}}); print -l $a
./dir2.1
./dir2.2
./dir2.2/file3.1
./file2.1
At last, iterating over current dir's dirs could do the job, too:
a=(); dir=;
for dir in *(/); do
pushd "$dir"
a+=(./**/*)
popd
done
print -l $a
#=> ./dir2.1
./dir2.2
./dir2.2/file3.1
./file2.1
Here are some zsh documents.
(1)x~y
glob operator:
x~y
(Requires
EXTENDED_GLOB
to be set.) Match anything that matches the pattern x but does not match y. This has lower precedence than any operator except‘|’
, so‘*/*~foo/bar’
will search for all files in all directories in‘.’
and then exclude‘foo/bar’
if there was such a match. Multiple patterns can be excluded by‘foo~bar~baz’
. In the exclusion pattern (y),‘/’
and‘.’
are not treated specially the way they usually are in globbing.
(2)e
string glob qualifier:
e
string
+
cmd...
During the execution of string the filename currently being tested is available in the parameterREPLY
; the parameter may be altered to a string to be inserted into the list instead of the original filename.