beforehand: this evolved from a general question (I thought it was the case with every folder) to this specific corner case.
as it seemed that a few other people are as curious as myself, I decided to not delete this question but rework it.
Let's say I want to list all items contained direct in my home folder, except the dot files & folders. IMHO, the following find command should do this (I explicitly wrote "dot items", because if you use a .hidden
file the "hidden items" may differ):
find $HOME/ -mindepth 1 -maxdepth 1 -path "$HOME/.*" -prune -o -print
-mindepth 1
to ensure that $HOME itself is not included, -maxdepth 1
to limit to one folder level, -path "$HOME/.*" -prune
to exclude everything in the home folder starting with a dot and -o -print
to ensure that not the excluded elements are in the output according to the man page.
So far, so good. It kind of works (even if there are dot-folders for which you don't have the permissions), but it starts to choke on $HOME/.gvfs
although it shouldn't be accessed at all due to -path ... -prune
:
/home/user/file a
/home/user/folder b
...
find: '/home/user/.gvfs': Permission denied
/home/user/.gvfs
...
/home/user/file z
I know I can use -perm
at find
and 2> /dev/null
to work around it, but what really appalled me is, that /home/user/.gvfs
is included in the list (although all other dot items are excluded)!!!
and I'm pretty sure some not so careful people are tempted to get rid of the error with 2> /dev/null
and move on. …if they recognize the error at all, because at longer lists with a few 100 lines you have to explicitly check for errors with something like adding 1> /dev/null
prior to working with that command.
really ignoring a directory/tree?
Why is find
trying to access /home/user/.gvfs
at all, despite -maxdepth 1
or at least -prune
should prevent this? e.g. from the man page:
‘-prune’ action (which only prevents further descent […])
even in an older man page (installed on my system) for -path
it is stated:
To ignore a whole directory tree, use -prune rather than checking every file in the tree.
dangerous listing of an "ignored" directory itself!
If all /home/user/.*
would be really ignored, perhaps the second part of my question won't be necessary, as it seems to me this is because of an internal error:
why is /home/user/.gvfs
still in the list and how to prevent it? Is there a way without fiddling around with -user
/-perm
and 2> /dev/null
for every find
command (at least with a -prune
action)?
future usage -> efficiency?
What is really ignored with -prune
(or at least with -path ... -prune
)?
If with -prune
still (nearly) all files are checked, I would ditch using this complicated option and rather use !
or -not
, especially as -prune
is apparently much more error-prone.
Best Answer
This appears to be a bug in the GNU implementation of
find
. Here, the trigger seems to be the fact that thelstat()
/fstatat()
system call (to retrieve a file's metadata) fails withEACCESS
(permission denied) on that.gvfs
file.That typically happens on Linux for files that are mount points of FUSE-based file systems for which the
-o allow-other
option has not being used. That can also happen withfind -L
/find -follow
when there are symlinks that point to inaccessible areas.I find here that
-prune
for those files returns false which is clearly a bug as-prune
is documented (and required by POSIX) to always return true.As a work around until that bug is fixed, you can replace
-prune
with'(' -prune -o -true ')'
(note that-true
is a GNU extension; a portable equivalent could be! -name ''
which is guaranteed to be true, though you could also do'(' -prune -o ! -prune ')'
)Now, there are a few issues with your code itself. In:
$HOME/
should be quoted as otherwise the$HOME
expansion is subject to split+glob. You could also write it~/
.-prune
is to tellfind
not to descend into a directory. Here it's superfluous asmaxdepth 1
will stopfind
from descending into any directory (other than the top-level (depth 0) one$HOME/
).-path
takes a pattern. That means that the contents of$HOME
will be taken as a pattern. If your$HOME
is for instance/home/[112] me
,-path '/home/[112] me/.*'
would match on/home/2 me/.foo
, but not on/home/[112] me/.foo
. Also matching on the full path here is not necessary, you can match on the file name instead with-name '.*'
.*
with GNUfind
only matches on characters, so.*
matches on filenames that start with.
and are also made of sequences of bytes that form valid characters. So you can only use that to match hidden files in locales where every sequence of byte form valid characters (such as theC
locale).So here, if the point is to list the non-hidden files in your HOME directory with their full paths, you'd do:
Which would also avoid that GNU
find
bug.Or the standard equivalent (though adds a
/./
in the paths):(here, the GNU
find
bug would be hit for files that do not start with.
and are notlstat()
able, but you'll see at that bug description above that whether non-lstat()
able files are reported or not varies greatly with the implementation).Here, I'd just use
zsh
's:Or if you don't want them sorted like with
find
: