46

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?

1

4 Answers 4

90

Just use namei:

$ namei d
f: d
 l d -> c
   l c -> b
     l b -> a
       d a
4
  • 5
    TIL. This is a cool command. upvotes Nov 22, 2011 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, 2016 at 8:51
  • Indeed, was revisiting this post and namei is a wonderful command!
    – Kalecser
    Mar 30, 2017 at 17:56
  • 1
    use with "which" command to find the actual usage of a program: $namei `which java`
    – pdem
    Apr 4, 2017 at 8:52
21

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)
3
  • On OS X: brew install coreutils and greadlink -e <link>
    – Jose Alban
    May 13, 2016 at 9:54
  • On macOS use readlink -f <file> instead. There is no -e option. Better than install GNU coreutils just to use this one command. May 19, 2022 at 16:11
  • Not sure how this answers the question…it only prints the final file path, not all the intermediates. Jan 1, 2023 at 1:56
4

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
}
2
  • 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, 2010 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 Oct 20, 2010 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 "->"

You must log in to answer this question.

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