1. Overview
In this tutorial, we’ll see how to find broken symlinks using the find command in different forms.
2. Symlinks
Symlinks, symbolic links, or even soft links are files that point to other links, files, directories, and resources. They are similar to shortcuts in Windows.
Since they are links, once the target is not available anymore, they become useless.
Our examples will run in the following directory structure:
To reproduce the above environment, we can run these commands in an empty directory:
mkdir -p baeldung/dir-1/dir-2/dir-3/
touch baeldung/file.txt
touch baeldung/dir-1/file-1.txt
touch baeldung/dir-1/dir-2/file-2.txt
touch baeldung/dir-1/dir-2/dir-3/file-3.txt
ln -s dir-2/dir-3/ baeldung/dir-1/dir-3
ln -s ../../file-1.txt baeldung/dir-1/dir-2/dir-3/file-1.txt
ln -s baeldung/nonexistent-directory baeldung/dir-1/dir-2/link-to-nonexistent-directory
ln -s dir-4/file-4.txt baeldung/dir-1/dir-2/dir-3/file-4.txt
ln -s dir-2/file-2.txt baeldung/dir-1/file-2.txt
ln -s ../filex.txt baeldung/dir-1/filex.txt
ln -s ../cyclic-link baeldung/dir-1/cyclic-link
ln -s dir-1/cyclic-link baeldung/cyclic-link
ln -s . baeldung/infinite-loop
3. Finding Broken Symlinks
The find command has the following structure:
find [-H] [-L] [-P] [-D debugopts] [-Olevel] [starting-point...] [expression]
The -H, -L and -P options control how symbolic links are treated, and when omitted, use -P as the default.
When -P is used and find examines or prints information from a symbolic link, the details are taken from the properties of the symbolic link itself.
We’re going to assume a Bash shell for our commands.
3.1. The Simple Way
The simplest way to detect broken symlinks is by using the following command:
find baeldung -xtype l
Executing the above command in our environment will produce the following output:
find: ‘baeldung/cyclic-link’: Too many levels of symbolic links
find: ‘baeldung/dir-1/cyclic-link’: Too many levels of symbolic links
baeldung/dir-1/filex.txt
baeldung/dir-1/dir-2/link-to-nonexistent-directory
baeldung/dir-1/dir-2/dir-3/file-4.txt
In the first two lines, we can see that not only we’re detecting broken symlinks, but we’re also reporting cyclic links. After the cyclic links, we see our broken links.
We used the -xtype l argument, which is true only for broken symbolic links.
We could have achieved the same thing by including the -P flag:
find baeldung -P -xtype l
As we might guess, this means that -P is the default.
3.2. The Portable Way
For POSIX-compliant shells, we can use the solution below since -xtype may not be available on all systems:
find baeldung -type l ! -exec test -e {} \; -print
As expected, the output will be the same as the last sample:
baeldung/cyclic-link
baeldung/dir-1/cyclic-link
baeldung/dir-1/filex.txt
baeldung/dir-1/dir-2/link-to-nonexistent-directory
baeldung/dir-1/dir-2/dir-3/file-4.txt
Let’s break in parts what is happening here:
- The find command is looking for files of type link the same way we did in the last samples
- ! is negating the result of the –exec expression that is testing for files existence – negating that will show only files that don’t exist
- The exec expression action is used to execute commands for each found file. In this case, we’re executing test -e for each file. test is a Linux command used to check file types and compare values. Using the -e argument, we’re checking if the file exists
- {} will be replaced by the current filename
- \* is used to protect the command from expansion by the shell – the command can be quoted to avoid the use of “\*“
- ; represents the end of the command
- -print prints the result to the standard output
3.3. The Portable and In-Depth Way
Or, we can find this standard solution on many websites:
find -L baeldung -type l
In fact, it works even better than other solutions above. This is because it also reports “file system loops” as we can see on the second line:
find: ‘baeldung/cyclic-link’: Too many levels of symbolic links
find: File system loop detected; ‘baeldung/infinite-loop’ is part of the same file system loop as ‘baeldung’
find: ‘baeldung/dir-1/cyclic-link’: Too many levels of symbolic links
baeldung/dir-1/dir-3/file-4.txt
baeldung/dir-1/filex.txt
baeldung/dir-1/dir-2/link-to-nonexistent-directory
baeldung/dir-1/dir-2/dir-3/file-4.txt
The side effect that many people don’t know is that this approach does an in-depth search if a link points to a directory.
For example, imagine that we have a link that points to /usr/share. Using the -L option will make find traverse the entire /usr/share structure.
This approach can take too much time and resources to complete. In most cases, it is not what we’re trying to do. It’s essential to know when to use each approach and have in mind its benefits and consequences.
So, if we want to search for broken links inside directories when we have links that point to directories, we can use the -L option.
But if we want to stay inside our specific directory structure and don’t want to follow links, we don’t need to use -L.
To see this difference, we can enable the debug to show each file and folder visited:
find -D search baeldung -xtype l
find -D search -L baeldung -type l
3.4. The Hacky Way
We can use a different approach that uses the user permission level to read files.
It’s a bit of a hack since we’ll see broken links as well as files we don’t have permission to read. In other words, this option only really is applicable for root users.
That said, we can use the below command to find broken links**:**
find baeldung -type l ! -readable
That will produce an output similar to the last sample:
baeldung/cyclic-link
baeldung/dir-1/cyclic-link
baeldung/dir-1/filex.txt
baeldung/dir-1/dir-2/link-to-nonexistent-directory
baeldung/dir-1/dir-2/dir-3/file-4.txt
In this case, we used -type l that looks for files of type symlinks.
Then we negate the -readable argument with !. This will then show files that we can’t read.
Since we can’t read broken links, these are returned as part of the result.
3.5. Limiting the Scope and Depth
We can limit the scope of our search using some useful test expressions:
- lname pattern: We can provide link name patterns
- ilname pattern: The same as lname, but is case-insensitive
- maxdepth: Search until this directory depth
- mindepth: Search from this directory depth
As an example, we can execute:
find baeldung -mindepth 3 -ilname "*.TXT" -xtype l
Which will produce the output:
baeldung/dir-1/dir-2/dir-3/file-4.txt
3.6. Customizing the Output
We can customize the output to provide more useful information.
With the expression action -ls, we can have detailed information about the link and to where it points to:
find baeldung -xtype l -ls
And the output will be:
find: ‘baeldung/cyclic-link’: Too many levels of symbolic links
find: ‘baeldung/dir-1/cyclic-link’: Too many levels of symbolic links
21109100 0 lrwxrwxrwx 1 fabio fabio 12 Sep 17 23:26 baeldung/dir-1/filex.txt -> ../filex.txt
21109086 0 lrwxrwxrwx 1 fabio fabio 30 Sep 17 23:21 baeldung/dir-1/dir-2/link-to-nonexistent-directory -> baeldung/nonexistent-directory
21109087 0 lrwxrwxrwx 1 fabio fabio 16 Sep 17 23:21 baeldung/dir-1/dir-2/dir-3/file-4.txt -> dir-4/file-4.txt
This approach doesn’t work well when filenames are unusual, because they can contain any character except \0 and /, even line breaks.
Unusual characters in a file name can do unexpected and often undesirable things to our terminal. For example, certain characters can change the settings of our function keys on some terminals.
4. Conclusion
In this quick article, we saw how to find broken links using simple and more elaborate approaches, how to limit the scope of the search, and customize the output.
The find utility is a real swiss army knife! To learn more, please take a look at the find man page.