From what I understand about the read permission on directories, it allows listing of which files are in a directory and that's about it.

Given a directory with 0744 permissions, owned by userA:

[userA@localhost ~]$ mkdir -m 0744 /tmp/semi-secret
[userA@localhost ~]$ ls -ld /tmp/semi-secret/
drwxr--r--. 2 userA userA 6 Aug 29 10:15 /tmp/semi-secret/
[userA@localhost tmp]$ touch semi-secret/foobar.txt
[userA@localhost tmp]$ chmod 0600 semi-secret/foobar.txt

To userB, The existence of the file foobar.txt is apparent from the ls command.

[userB@localhost ~]$ ls -l /tmp/semi-secret/
ls: cannot access /tmp/semi-secret/foobar.txt: Permission denied
total 0
-????????? ? ? ? ?            ? foobar.txt

But why does the test -e command exit with a non-zero status?! Its only job is to confirm if a file exists or not, and directory permissions are supposed to allow that.

[userB@localhost ~]$ test -e /tmp/semi-secret/foobar.txt || echo "The file doesn't exist."
The file doesn't exist.

Since even the stat(2) system call cannot work for any file in a directory that misses the x bit in the permissions, anything that is based on the stat(2) system call will fail in such a directory.

If your directory has the r bit set, you are indeed able to read the content of that directory usingreaddir(), but you cannot stat or open any file.

In contrary, a directory that has the x bit set, allows you to stat(2) or open files if you know the related file names but you cannot use readdir() to retrieve the content of the related directory.

test -e exits with a non-zero status in case that either the file does not exist (after symlink resolution, so that also applies to existing symlinks to non-existing or non-accessible files) or any error occurs while trying to stat(2) the related file.

| improve this answer | |

To complement @schily's fine answer, one way to check that a given directory for which you have read access but not necessarily search access, contains an entry by a given name is to read the content of that directory and look for that file name.

You can use shell globbing for that.

set -- /tmp/semi-secret/[f]oobar.txt
if [ "$1" = /tmp/semi-secret/foobar.txt ]; then
  echo exists

With zsh, you can also do:

if ()(($#)) /tmp/semi-secret/foobar.txt(N); then
  echo exists

With find implementations that support -maxdepth:

if find /tmp/semi-secret -mindepth 1 -maxdepth 1 -name foobar.txt -print |
     grep -q '^'; then
  echo exists

Except for the zsh solution, it's hard to adapt to arbitrary file names though.

| improve this answer | |

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.