How to list symbolic link chains?

60,723

Solution 1

Just use namei:

$ namei d
f: d
 l d -> c
   l c -> b
     l b -> a
       d a

Solution 2

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)

Solution 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
}
Share:
60,723

Related videos on Youtube

Kalecser
Author by

Kalecser

Updated on September 17, 2022

Comments

  • Kalecser
    Kalecser over 1 year

    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?

  • Kalecser
    Kalecser about 14 years
    I've tested your script and it really works but I prefer something simpler so I've accepted the other answer even if incomplete.
  • Stefan Lasiewski
    Stefan Lasiewski over 13 years
    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
  • Tom O'Connor
    Tom O'Connor over 12 years
    TIL. This is a cool command. upvotes
  • Jose Alban
    Jose Alban about 8 years
    On OS X: brew install coreutils and greadlink -e <link>
  • phobic
    phobic over 7 years
    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!
  • Kalecser
    Kalecser about 7 years
    Indeed, was revisiting this post and namei is a wonderful command!
  • pdem
    pdem about 7 years
    use with "which" command to find the actual usage of a program: $namei `which java`