Test -e not working as expected (by me)

I ran into the following and still do not understand entirely the rationale behind this. If someone could explain why things are as they are I'd be thankful.

The following was tested on AIX 7.1 with ksh88, but i suspect that to be ubiquitous. In an installation routine i had to create a set of symbolic links. Because they might already exist i used test -e filename to test it:

if [ ! -e /some/link ] ; then
     ln -s /path/to/file /some/link
fi

This didn't work as expected because test returned 0 only if the link AND its target existed. If only the link exists but not the file referenced by it test will return 1.

The POSIX Documentation about test says:

-e  pathname
    True if pathname resolves to a file that exists. False if pathname cannot be resolved.

A "file that exists" is IMHO covered by a link, even if this link points to a file which doesn't. I can stat() this file and do many other operations on it which can be done with a file.

I do understand that i can use "-L", which tests for a link but i would like to understand the rationale behind the behavior. Or is the POSIX documentation inconsistent here?

bakunin

Single UNIX Specification v3

---------- Post updated at 01:30 PM ---------- Previous update was at 01:18 PM ----------

It seems to be POSIX-compliant behaviour. Look at this excerpt:

and -e tests, if a pathname resolves to a file that exists, not that pathname exists.

Man pages, as e.g. in AIX:

or in Linux:

are lying :wink:

Consider testing for a link instead:-

$ ls -l a b
ls: cannot access a: No such file or directory
lrwxrwxrwx 1 RBATTE1 TS 1 Mar 11 13:32 b -> a

$ if [ -L b ]
> then
>   echo hit
> else
>   echo miss
> fi
hit

See my first post above, i know that. The question is: why does "-e" work like it does and: is the POSIX documentation buggy there? According to the definition of "file" it should work like this:

if "file" exists and:

  • is a regular file, "-f" will return TRUE
  • is a block special file "-b" will return TRUE
  • is a directory "-d" will return TRUE
    ...etc. analogously for "-c", "-h", "L", "-p", ...

and "-e" should return TRUE if any one of the above return TRUE.

bakunin

I suppose it all depends on your definition of what is a file in "resolves to a file". Does it mean a file being with content and just a plain regular file, or does it mean any entity within a directory. How about a directory even? That does seem to be considered as a file.

It should be that everything is a file, but the word resolves perhaps covers the symbolic link issue that's being illustrated.

All very wordy stuff. I bet a lawyer would love it.

Kind regards,
Robin

Robin is correct. It all depends on what resolves means. And, the standards lawyers love stuff like this. And, the text agenda.kgb quoted:

is the key here. If the function you're using is stat (), then pathname resolution is NOT complete when the final component of a path names a symbolic link and the file named by the symlink does not exist. If the function you're using is lstat (), then pathname resolution is complete because the description of the stat () and lstat () functions explicitly states:

And, in the Commands and Utilities Volume of the POSIX standards, pathname resolution is not complete when a symlink is found at the end of the path unless the utility description explicitly states that a symbolic link is being processed rather than the file the symlink references. The description of test -L pathname is:

which makes it clear that for the -L option, test needs to use the equivalent of lstat(pathname, ...) . But, the description of test -e :

does not make any exception for symlinks. So, it needs to use the equivalent of stat(pathname, ...) .

Speaking as a standards lawyer, it appears to me that the ksh88 AIX 7.1 test -e and test -L utility is behaving properly.

3 Likes

I did some tracings.
Indeed test -L does an lstat() i.e. does not try to follow a symlink.
test -e does either a stat() or an access() i.e. follows a symlink and a symlink's symlink... This is meant with "resolve".
In order to test for anything, you need test -L "$file" || test -e "$file" .