6

I know that some shells at least support file test operators that detect when a filename names a symlink.

Is there a POSIX utility1 that provides the same functionality?


1 I may not be using the right terminology here. What I mean by "utility" is a free-standing executable living somewhere under /bin, /usr/bin, etc., as opposed to a shell built-in.

1
  • are you thinking about file command ? see man file
    – Archemar
    Commented Sep 10, 2015 at 14:00

4 Answers 4

21

You're looking for test:

-h pathname

True if pathname resolves to a file that exists and is a symbolic link. False if pathname cannot be resolved, or if pathname resolves to a file that exists but is not a symbolic link. If the final component of pathname is a symlink, that symlink is not followed.

Most shells have it as a builtin, but test also exists as a standalone program, which can be called from other programs without invoking an intermediate shell. This is the case for most builtins that shells may have, except for those that act on the shell itself (special builtins like break, export, set, …).

[ -h pathname ] is equivalent to test -h pathname; [ works in exactly the same way as test, except that [ requires an extra ] argument at the end. [, like test, exists as a standalone program.

For example:

$ ln -s foo bar
$ /usr/bin/test -h bar && echo y
y
4
  • Correct me if I'm wrong, but the solution you propose is failing to tell that the "filename names a symlink" when the symlink does not resolve to a file that exists. Commented Sep 10, 2015 at 15:44
  • @maxime.bochon I just tested it and it exits with 0 (successful), when the target of the symlink doesn't exist.
    – Kritzefitz
    Commented Sep 10, 2015 at 17:34
  • 1
    @maxime.bochon yes. The solution only deals with the specific file you are testing. It will tell you whether it is a link or not. Whether the target exists or not is something completely different. A broken link is still a link.
    – terdon
    Commented Sep 10, 2015 at 17:56
  • confirmed. mkdir a; ln -s a b; ln -s c d; test -h b && echo link; test -h c || echo notlink; test -h d && echo link correctly outputs link notlink link Commented Sep 10, 2015 at 17:59
3

Two utilities could do that for you, fileand readlink:

  1. file some_symlink will display some_symlink: symbolic link to 'some_target'
  2. readlink some_symlink will exit with code 0 whereas readlink some_file will exit with code 1

Note that exit code is stored in variable $?, and can be displayed with echo $?.

1
1

There is also stat:

$ touch test
$ ln -s test test_l

$ stat test
  File: `test'
  Size: 0           Blocks: 0          IO Block: 4096   regular empty file
Device: fc00h/64512d    Inode: 4309        Links: 1
Access: (0664/-rw-rw-r--)  Uid: ( 1000/ vagrant)   Gid: ( 1000/ vagrant)
Access: 2015-09-11 11:37:59.864165922 +0000
Modify: 2015-09-11 11:37:59.864165922 +0000
Change: 2015-09-11 11:37:59.864165922 +0000
 Birth: -

$ stat test_l
  File: `test_l' -> `test'
  Size: 4           Blocks: 0          IO Block: 4096   symbolic link
Device: fc00h/64512d    Inode: 7179        Links: 1
Access: (0777/lrwxrwxrwx)  Uid: ( 1000/ vagrant)   Gid: ( 1000/ vagrant)
Access: 2015-09-11 11:38:07.220173955 +0000
Modify: 2015-09-11 11:38:07.220173955 +0000
Change: 2015-09-11 11:38:07.220173955 +0000
 Birth: -

$ stat -c "%F" test
regular empty file

$ stat -c "%F" test_l
symbolic link
-2

Alternatively you can use a simple script:

#!/bin/bash    
ls -alFQ | grep '^l'

The Unix mantra is to have small simple utilities that can be chained together to achieve complex task. So while this is technically not a POSIX utility, it's very close to one.

If you want to get fancy you can only select what portion of the string to return:

#!/bin/bash    
ls -alFQ | grep '^l' | tr -s ' ' | cut -d ' ' -f 11

The -f 11 returns the file the link is pointing to, and -f 10 return the name of the link. This only works if the files names have no spaces in them, otherwise it gets tricky - it can probably be done, but you'll have to escape the white spaces or use a pattern, which is more than I care to write at the moment.

If you want something that returns 0/1:

    #!/bin/bash
    if [[ -n $(ls -alFQ | grep '^l') ]]; then
        exit
    else
        exit 1
    fi
6
  • 3
    Parsing the output of ls is, in general, a bad idea. Unusual filenames can wreck all sorts of havoc on your attempts to interpret things.
    – Mark
    Commented Sep 10, 2015 at 20:13
  • You are right, having spaces in the file names will cause problems, I'll update my answer. It's probably doable if you use ls -b to escape the whitespaces, but then you'll have to cut it appropriately ...
    – ventsyv
    Commented Sep 10, 2015 at 20:20
  • 1
    It's not just spaces. What if someone creates a file named filename\nlrw-r--r-- 1 root wheel 43 Nov 10 2014 .htaccess, ie. with a newline and a directory listing in the name?
    – Mark
    Commented Sep 10, 2015 at 20:30
  • This is beyond the original question, but yes it can be done safely, although some creative scripting / regex usage will be required. If you want to start another question I can go in more details there.
    – ventsyv
    Commented Sep 10, 2015 at 20:44
  • Having thoroughly tested your suggestions, your first one fails on filenames containing an "l" after a newline in their name, your second one fails on filenames with spaces, and your third (in addition to the filename issue) has the unfortunate side effect of logging the user out if run standalone.
    – Mark
    Commented Sep 11, 2015 at 1:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .