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?
Server Fault is a question and answer site for system and network administrators. It only takes a minute to sign up.
Sign up to join this communityGiven 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?
Just use namei
:
$ namei d
f: d
l d -> c
l c -> b
l b -> a
d a
ls
. Thanks!
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)
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
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
}
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
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 "->"