7

I'd like to know if I can rely on the behavior I am seeing when using find with -depth option and user does not have execute permission for a subdirectory.

Assume the following directory structure:

drwxrwxrwt. 10 root    root     12288 Mar 14 04:31 .
dr-xr-xr-x. 24 root    root      4096 Dec  6 03:33 ..
drwx------   4 root    root      4096 Mar 14 04:03 jen

Run the following command as non-root user:

find -type d

The output is:

.
./jen
find: `./jen': Permission denied

So find found the directory jen and output that. It then tried to descend into jen, but didn't have permission so it printed the error. The first line above is printed to stdout and the second line to stderr.

Now run the following as non-root user:

find -depth -type d

And the output is:

find: `./jen': Permission denied
.

So the pathname is not output to stdout unless the user has permission to list the contents of the directory.

This output is perfect for what I want to do. However, I'm not sure if this is just a coincidence or not. Can I rely on this behavior?

I'm using GNU findutils 4.4.2. I'm wondering if this behavior is the same across all versions of find. And if not, is it at least the same across all versions of GNU find.

It doesn't matter to me (for this use case) whether or not jen is printed in the first example. I'm just wondering if I can depend on it being excluded when -depth is used. Normally, it's not a good idea to rely on undocumented behavior. But, to me, this side effect makes sense. So I'm thinking that this might be the intended behavior.

The manual says:

— Option: -depth

Process each directory's contents before the directory itself.

This is exactly what I want, but it's not clear that the pathname of the directory itself will be excluded from the output if it doesn't descend into it.

Thanks to a hint from Hauke Laging, I discovered that I can list only directories and explicitly exclude directories where permission to list their contents is denied:

find -type d \( \( -type d \( \! -executable -or \! -readable \) \) -prune -or -print \)

This also has the effect of stopping the "Permission denied" errors because find never tries to descend into a directory if it doesn't have permission.

Unfortunately, there are two reasons why this will not work for my needs.

  • I want the error messages
  • I need the -depth option

Quoting from the manual

If the ‘-depth’ option is in effect, the subdirectories will have already been visited in any case. Hence ‘-prune’ has no effect in this case.

So I am back to where I started.

It's still not clear whether or not "Process each directory's contents before the directory itself." also means "Don't process the directory if you can't process it's contents".

  • Which versions of find do you want to consider? On Solaris 10, find . -type d prints "." to stdout and "find: cannot read dir ./jen: Permission denied" to stderr. find . -depth -type d prints "find: cannot read dir ./jen: Permission denied" to stderr and "." to stdout. Neither print "./jen" to stdout. – Mark Plotnick Mar 14 '14 at 16:06
  • @MarkPlotnick The missing . was a copy and paste error. I've updated my question with details about versions and what details I'm hoping I can count on. – toxalot Mar 14 '14 at 16:24
  • @MarkPlotnick I find it strange that Solaris 10 does not print ./jen for find . -type d. It's still part of the contents of . even though find can't descend into it. What does ./* glob pattern expand to on Solaris 10? Does it include ./jen? – toxalot Mar 15 '14 at 1:54
  • I found it unexpected, too, but that's what it does. echo ./* outputs ./jen. I set up the directory structure to be the same as the OP's. truss shows that find does fchdir() to the open "." fd, then getdents64() = 72, lstat64("jen",...) = 0, openat(..., "jen", ...) => EACCES, then it outputs the error message, then getdents64() = 0, then cleanup and exit. This is on the most recent free Solaris 10/x86 distro from Oracle, Generic_147148-26, in a ufs partition – Mark Plotnick Mar 17 '14 at 14:40
2

Probably the better approach is to handle such directories explicitly. I don't know whether these are standard features but at least with Gnu find this is possible:

find . \( -type d \( \! -executable -or \! -readable \) \) -prune -or -type d

of course, it is also possible to print a message if a directory is ignored:

find . \( -type d \( \! -executable -or \! -readable \) \) \
  -printf "Permission denied: %p\n" -prune -or -type d -print
  • This does not give desired output. The directory structure in question was an example. The real structure includes regular files. With the -or print, all file types are printed. I only want directories. – toxalot Mar 14 '14 at 14:45
  • In addition to the need for excluding regular files, I also want to keep the error messages. I've updated my question to reflect this. – toxalot Mar 14 '14 at 16:01
  • @toxalot I guess it's obvious that -print (i.e. the part after the -or) is just a dummy which needs to be replaced by whatever you want. E.g. you may change that to -or -type d. – Hauke Laging Mar 14 '14 at 16:31
  • The second prints only directories, excludes directories if no permission to list contents, and prints an error message, which gets me most of the way there. Note that the -print after the -or -type d is important. You can't just replace -or -print with -or type d as suggested in the comment. However, since -prune has no effect when -depth is used, I'm back to my original question. – toxalot Mar 14 '14 at 17:06
  • @toxalot You can adapt the -printf part to whatever you want. Of course, you can print one line with the path and another with the error message. -print is needed after the -or only if a print command occurs in the first part. You do not need -prune (descending into those directories is impossible anyway) so you can use depth without problems. – Hauke Laging Mar 15 '14 at 0:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.