Server Fault is a question and answer site for system and network administrators. 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

Given this example:

mkdir a
ln -s a b
ln -s b c
ln -s c d

If I execute:

ls -l d

It will show:

d -> c

Is there a way for ls or any other linux command to show d -> c -> b -> a instead?

share|improve this question
I cross posted this as… I hope you don't mind. – Paul Feb 23 '10 at 17:30
up vote 11 down vote accepted

readlink -e <link>

readlink [OPTION]... FILE

  • -e, --canonicalize-existing
    canonicalize by following every symlink in every component of the given name recursively, all components must exist
$ mkdir testlink
$ cd testlink
pjb@pjb-desktop:~/testlink$ ln -s c b
pjb@pjb-desktop:~/testlink$ ln -s b a
pjb@pjb-desktop:~/testlink$ ls -l 
total 0
lrwxrwxrwx 1 pjb pjb 1 2010-02-23 08:48 a -> b
lrwxrwxrwx 1 pjb pjb 1 2010-02-23 08:48 b -> c
pjb@pjb-desktop:~/testlink$ echo foo > c
pjb@pjb-desktop:~/testlink$ cat a
pjb@pjb-desktop:~/testlink$ readlink -e a

note: readlink a by itself returns b

note #2: together with find -l, a utility to list the chains could easily be written in perl, but also has to be smart enough to detect loops

readlink will not output anything if you have a loop. This is better than getting stuck, I suppose.

pjb@pjb-desktop:~/testlink$ ln -sf a c
pjb@pjb-desktop:~/testlink$ ls -l 
total 0
lrwxrwxrwx 1 pjb pjb 1 2010-02-23 08:48 a -> b
lrwxrwxrwx 1 pjb pjb 1 2010-02-23 08:48 b -> c
lrwxrwxrwx 1 pjb pjb 1 2010-02-23 09:03 c -> a
pjb@pjb-desktop:~/testlink$ readlink -e a
pjb@pjb-desktop:~/testlink$ # (note: no output)
share|improve this answer
On OS X: brew install coreutils and greadlink -e <link> – Jose Alban May 13 at 9:54

just use namei:

$ namei d
f: d
 l d -> c
   l c -> b
     l b -> a
       d a
share|improve this answer
TIL. This is a cool command. upvotes – Tom O'Connor Nov 22 '11 at 14:52

Here is a recursive function in Bash:

chain() { export chain; local link target; if [[ -z $chain ]]; then chain="$1"; fi; link=$(stat --printf=%N $1); while [[ $link =~ \-\> ]]; do target="${link##*\`}"; target="${target%\'}"; chain+=" -> $target"; chain "$target"; return; done; echo "$chain"; unset chain; }

On multiple lines:

chain() {
    export chain
    local link target
    if [[ -z $chain ]]
    link=$(stat --printf=%N "$1")
    while [[ $link =~ \-\> ]]
        chain+=" -> $target"
        if [[ ! $target =~ / && $1 =~ / ]]
        chain "$target"
    echo "$chain"
    unset chain


$ chain d
d -> c -> b -> a
$ chain c
c -> b -> a
$ chain a

It requires stat(1) which may not be present on some systems.

It will fail if names contain backticks, single quotes, or "->". It gets stuck in a loop with symlink loops (this could be solved using an associative array in Bash 4). It exports a variable called "chain" without regard to whether it's already in use.

There may be other problems with it.


Fixed a problem with some relative symlinks. Some still don't work, but the version below doesn't require the target of the link to exist.

Added a version that uses readlink:

chain ()
    export chain;
    local target;
    if [[ -z $chain ]]; then
    target=$(readlink "$1");
    while [[ $target ]]; do
        chain+=" -> $target";
        if [[ ! $target =~ / && $1 =~ / ]]
        chain "$target";
    echo "$chain";
    unset chain
share|improve this answer
I've tested your script and it really works but I prefer something simpler so I've accepted the other answer even if incomplete. – Kalecser Feb 23 '10 at 21:02
Nice script. Sometimes I want to see the entire chain, and readlink doesn't seem to show that. Java on Ubuntu is: /usr/bin/java -> /etc/alternatives/java -> /usr/lib/jvm/java-6-openjdk/jre/bin/java – Stefan Lasiewski Oct 20 '10 at 19:33

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.