36

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?

64

Just use namei:

$ namei d
f: d
 l d -> c
   l c -> b
     l b -> a
       d a
  • 5
    TIL. This is a cool command. upvotes – Tom O'Connor Nov 22 '11 at 14:52
  • This should be the accepted answer, as it shows the complete chain of links as requested in the op. Further it can be used to list an entire directory similar to ls. Thanks! – phobic Oct 21 '16 at 8:51
  • Indeed, was revisiting this post and namei is a wonderful command! – Kalecser Mar 30 '17 at 17:56
  • use with "which" command to find the actual usage of a program: $namei `which java` – pdem Apr 4 '17 at 8:52
18

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
foo
pjb@pjb-desktop:~/testlink$ readlink -e a
/home/pjb/testlink/c

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)
  • On OS X: brew install coreutils and greadlink -e <link> – Jose Alban May 13 '16 at 9:54
3

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 ]]
    then
        chain="$1"
    fi
    link=$(stat --printf=%N "$1")
    while [[ $link =~ \-\> ]]
    do
        target="${link##*\`}"
        target="${target%\'}"
        chain+=" -> $target"
        if [[ ! $target =~ / && $1 =~ / ]]
        then
            target="${1%/*}/$target"
        fi
        chain "$target"
        return
    done
    echo "$chain"
    unset chain
}

Examples:

$ chain d
d -> c -> b -> a
$ chain c
c -> b -> a
$ chain a
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.

Edit:

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
        chain="$1";
    fi;
    target=$(readlink "$1");
    while [[ $target ]]; do
        chain+=" -> $target";
        if [[ ! $target =~ / && $1 =~ / ]]
        then
            target="${1%/*}/$target"
        fi
        chain "$target";
        return;
    done;
    echo "$chain";
    unset chain
}
  • 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
  • 1
    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
0

You could just postprocess the output of namei with something like awk or grep to get just the lines you want:

namei d | awk '$1=="l"'

or

namei d | egrep -e "->"

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.