Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I'm writing a bash script that needs to distinguish a regular file from a symlink. I thought I could do this with if/test expression, but it doesn't work as I expected to:

$ touch regular_file
$ test -f regular_file; echo $?
$ test -h regular_file; echo $?
$ ln -s regular_file symlink
$ test -h symlink; echo $?
$ test -f symlink; echo $?

Why is that? And, how can I properly do this?

share|improve this question
up vote 7 down vote accepted

It looks like you are just scrambling your tests a bit. You don't need to run both test, the only one you need for this case is the -h one to tell you if the file is a symlink.

test -h file && echo "is symlink" || echo "is regular file"

The -f test only tells you if the object is a file. This would return 0 if it was a directory or a device node or a symlink to a directory, but will return 1 on a symlink to a file.

If you also needed to know if it was a symlink to a file rather than a directory, you would need to combine the results of both tests with a little bit of logic.

share|improve this answer
As I understood from the docs, the difference between -e and -f was that -e was used to know if a file (of any type) existed, and -f was specifically to test if the file existed and was a regular file. It seems I misunderstood what a "regular file" was.. – Nupraptor Oct 4 '11 at 12:24
@Nupraptor: Yes, you misunderstood the docs. A symlink is considered a regular file as opposed to some of the other types of nodes a file could be (block-device node, character-device node, directory, etc). If you want to know what TYPE of file it is you have to run a file type specific test like -h for symlinks, -p for named pipes, etc. – Caleb Oct 4 '11 at 12:35
Then, how do I test if a file is a regular file in the sense that is not a pipe nor a symlink, etc? Should I open another question for this? – Nupraptor Oct 4 '11 at 14:04
@Nupraptor: The only weird case is a symlink that links to a regular file. Otherwise, if it tests as a regular file, it's a regular file. – David Schwartz Oct 5 '11 at 12:06
test -f directory will return a 1 not a 0: test -f . ; echo $? (outputs 1) – polynomial Oct 6 '11 at 3:51

@Caleb is correct about making the script just test for the symlink. However the part about why was left out and I was curious. If you look at the coreutils source code and strace the output of test you can see that when you run the symbolic link test it uses lstat and if you using the -f test it actually calls 'stat' which follows the symlink:

$ ln -s varnish_config XXX
$ strace -s 2000 test -L XXX 2>&1 | grep XXX
execve("/usr/bin/test", ["test", "-L", "XXX"], [/* 47 vars */]) = 0
lstat("XXX", {st_mode=S_IFLNK|0777, st_size=14, ...}) = 0

$ strace -s 2000 test -L varnish_config 2>&1 | grep varnish
execve("/usr/bin/test", ["test", "-L", "varnish_config"], [/* 47 vars */]) = 0
lstat("varnish_config", {st_mode=S_IFREG|0664, st_size=1046, ...}) = 0

$ strace -s 2000 test -f XXX 2>&1 | grep XXX
execve("/usr/bin/test", ["test", "-f", "XXX"], [/* 47 vars */]) = 0
stat("XXX", {st_mode=S_IFREG|0664, st_size=1046, ...}) = 0

From the stat man page:

   stat() stats the file pointed to by path and fills in buf.

   lstat() is identical to stat(), except that if path is a symbolic link,
   then the link itself is stat-ed, not the file that it refers to.

This means the -f test will return true as long as the filename specified is a symlink to a regular file or a regular file itself.

share|improve this answer
Great explanation, thanks! – Nupraptor Oct 6 '11 at 13:38

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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