1. Overview

Sometimes, we may encounter a chain of symbolic links. We can follow the chain using the ls command, but it might be tedious to do so, especially if the chain is long.

In this tutorial, we’ll discuss how to list a chain of symbolic links.

2. Introduction to the Problem

As an example of a chain of symbolic links, we can check the java command:

$ which java
/usr/bin/java
$ ls -l /usr/bin/java
lrwxrwxrwx. 1 root root 22 Apr 21  2022 /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
lrwxrwxrwx 1 root root 64 Apr 21  2022 /etc/alternatives/java -> /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64/bin/java
$ ls -l /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64/bin/java
-rwxr-xr-x 1 root root 16016 Dec 31  2021 /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64/bin/java

As we see, the java command is in fact a symbolic link. The final target in the chain is /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64/bin/java. However, we had to use three ls commands to find the final target. It would be better if we could list the chain of symbolic links using a single command.

3. Example Setup

Let’s create a number of symbolic links to analyze the problem:

$ pwd
/tmp
$ mkdir directory1
$ touch directory1/file1
$ ln -s directory1/file1 link1
$ ln -s link1 link2
$ ln -s link2 link3

First, we created a directory, directory1, in the /tmp directory using the mkdir command. Then, we created a file, file1, in /tmp/directory1 using the touch command. Finally, we created three symbolic links. The first one, link1, is a symbolic link to directory1/file1. The second one, link2, is a symbolic link to link1. The final link, link3, is a symbolic link to link2. We created the symbolic links using the ln command with its -s option.

Now, let’s look at the chain of symbolic links we’ll be working with throughout the tutorial:

link3 -> link2 -> link1 -> directory1/file1

How can we list this chain on the command line? We’ll try to find several solutions in the remaining sections.

4. The namei Command

We can use the namei command for resolving a symbolic list chain:

$ namei link3
f: link3
 l link3 -> link2
   l link2 -> link1
     l link1 -> directory1/file1
       d directory1
       - file1

The f: part of the first line shows the pathname currently being resolved. It’s link3.

The l in the second line means that link3 is a symbolic link to link2. In a similar fashion, link2 is a symbolic link to link1, and link1 is a symbolic link to directory1/file1. That’s what the third and fourth lines show.

The d in the fifth line specifies that directory1 is a directory. Finally, the character in the last line points out that file1 is a regular file.

As we can see, namei follows the given pathname until it finds an endpoint that isn’t a symbolic link.

By default, namei displays the characters indented at the beginning of each line. The characters denote the type of each file in the chain. If we don’t want the indentation, we can use the -v option. This option aligns those characters vertically. We can also use the —vertical option instead of -v. They’re the same.

Let’s check the output of namei using the -v option:

$ namei -v link3
f: link3
l link3 -> link2
l   link2 -> link1
l     link1 -> directory1/file1
d       directory1
-       file1

The output is basically the same as the previous one. However, the first character on each line is aligned vertically this time, as expected.

The readlink command is another option for listing a chain of symbolic links.

Normally, if we use readlink without any options, it just shows the first component in the chain:

$ readlink link3
link2

Since link3 is a symbolic link to link2, it just displays link2.

However, we can use its -e option to show the last component of the chain:

$ readlink -e link3
/tmp/directory1/file1

Unfortunately, readlink doesn’t have an option that shows the whole chain like namei. However, we can use a script, follow_link.sh, for displaying the whole chain using readlink:

#!/bin/bash

current=$1

while [[ -L $current ]]
do
  next=$(readlink $current)
  dir_name=$(dirname $current)
  cd $dir_name
  echo "$current -> $next"
  current=$next
done

First, we get the path to be resolved as input and assign the path to the shell variable, current.

Then, we iterate the path in a while loop. The condition of the while loop, [[ -L $current ]], checks whether the file is a symbolic link. As long as it’s a symbolic link, the condition of the while loop is true. In that case, we get the next component in the chain using the statement next=$(readlink $current). We store the next node in the chain in the shell variable next.

Then, we extract the directory part of the filename using the dir_name=$(dirname $current) command. We store the path in the shell variable dir_name. We change the current directory to the directory stored in dir_name using the cd $dir_name statement because the symbolic link may not point to an absolute path.

Finally, after printing the current component of the chain using the echo “$current -> $next” statement, we update the current component of the chain for the next iteration. We achieve this using the current=$next statement.

Having understood the operation of the script, let’s give it a try:

$ pwd
/tmp
$ follow_link.sh /tmp/link3
/tmp/link3 -> link2
link2 -> link1
link1 -> directory/file1
$ follow_link.sh link3
link3 -> link2
link2 -> link1
link1 -> directory/file1

We ran the script twice. In the first run, the input of the script was an absolute path. However, it was a relative path in the second run. The outputs of the script in both runs were as expected.

Let’s try the script also for /usr/bin/java:

$ follow_link.sh /usr/bin/java
/usr/bin/java -> /etc/alternatives/java
/etc/alternatives/java -> /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-4.el8_5.x86_64/bin/java

In this case, all the links were absolute symbolic links. The result was again successful as expected.

6. Conclusion

In this article, we discussed how to list a chain of symbolic links.

First, we discussed the namei command, which is useful for displaying a symbolic link chain as a tree.

Then, we saw that we can show the final component of the symbolic link chain using the -e option of readlink. We also looked at a simple script that uses readlink to list the whole symbolic link chain.