How can I find broken symlinks
will give me all symbolic links, but makes no distinction between links that go somewhere and links that don’t. I’m currently doing:
find ./ -type l -exec file <> \; | grep broken
13 Answers 13
I’d strongly suggest not to use find -L for the task (see below for explanation). Here are some other ways to do this:
- If you want to use a «pure find » method, and assuming the GNU implementation of find , it should rather look like this:
find . -type l ! -exec test -e <> \; -print
find . -type l -exec sh -c 'file -b "$1" | grep -q "^broken"' sh <> \; -print
The find -L trick quoted by solo from commandlinefu looks nice and hacky, but it has one very dangerous pitfall: All the symlinks are followed. Consider directory with the contents presented below:
$ ls -l total 0 lrwxrwxrwx 1 michal users 6 May 15 08:12 link_1 -> nonexistent1 lrwxrwxrwx 1 michal users 6 May 15 08:13 link_2 -> nonexistent2 lrwxrwxrwx 1 michal users 6 May 15 08:13 link_3 -> nonexistent3 lrwxrwxrwx 1 michal users 6 May 15 08:13 link_4 -> nonexistent4 lrwxrwxrwx 1 michal users 11 May 15 08:20 link_out -> /usr/share/
If you run find -L . -type l in that directory, all /usr/share/ would be searched as well (and that can take really long) 1 . For a find command that is «immune to outgoing links», don’t use -L .
1 This may look like a minor inconvenience (the command will «just» take long to traverse all /usr/share ) – but can have more severe consequences. For instance, consider chroot environments: They can exist in some subdirectory of the main filesystem and contain symlinks to absolute locations. Those links could seem to be broken for the «outside» system, because they only point to proper places once you’ve entered the chroot. I also recall that some bootloader used symlinks under /boot that only made sense in an initial boot phase, when the boot partition was mounted as / .
So if you use a find -L command to find and then delete broken symlinks from some harmless-looking directory, you might even break your system.
Linux Commands – Find Broken Symlinks
The Kubernetes ecosystem is huge and quite complex, so it’s easy to forget about costs when trying out all of the exciting tools.
To avoid overspending on your Kubernetes cluster, definitely have a look at the free K8s cost monitoring tool from the automation platform CAST AI. You can view your costs in real time, allocate them, calculate burn rates for projects, spot anomalies or spikes, and get insightful reports you can share with your team.
Connect your cluster and start monitoring your K8s costs right away:
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:
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:
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:
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/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.