417

Is there a way to find all symbolic links that don't point anywere?

find ./ -type l

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

But I'm wondering what alternate solutions exist.

John Kugelman
  • 2,057
  • 2
  • 16
  • 23
gabe.
  • 11,784

13 Answers13

527

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 . -xtype l
    

    (xtype is a test performed on a dereferenced link)

  • portably (though less efficiently), you can also exec test -e from within the find command:

    find . -type l ! -exec test -e {} \; -print
    
  • Even some grep trick could be better (i.e., safer) than find -L, but not exactly such as presented in the question (which greps in entire output lines, including filenames):

    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...

  • This is something I hadn't considered @rozcietrzewiacz and is something that will definitely effect my particular case. Thanks for the thorough follow-up. – gabe. May 15 '12 at 15:46
  • I think this might be the answer to a major performance problem we've been having with a particular script. Thanks! – Kenny Rasschaert Jun 11 '12 at 09:37
  • 1
    This is awesome! I've been writing throw-away Python script for this particular one!! :-/ – Lester Cheung Oct 19 '12 at 00:02
  • 16
    I think -type l is redundant since -xtype l will operate as -type l on non-links. So find -xtype l is probably all you need. Thanks for this approach. – quornian Nov 17 '12 at 21:56
  • @quornian Great catch! Indeed, find -xtype l is enough. The only difference is in the number of system calls performed by each command (which indicates find -type l -xtype l should be faster). But I guess this would make a difference only on a large filesystem trees. – rozcietrzewiacz Nov 20 '12 at 20:08
  • I think it is not a good solution to grep for a string here. If you have a different locale, the output is in a different language, and you grep expression will fail. – guettli Jul 01 '14 at 17:41
  • 1
    I don't understand why -xtype l works. find . -type l -xtype l means "find all the symlinks to symlinks", rather than, "find all the symlinks to files that don't exist", right? – Flimm Oct 07 '14 at 12:53
  • 4
    Be aware that those solutions don't work for all filesystem types. For example it won't work for checking if /proc/XXX/exe link is broken. For this, use test -e "$(readlink /proc/XXX/exe)". – qwertzguy Jan 08 '15 at 21:37
  • 10
    @Flimm find . -xtype l means "find all symlinks whose (ultimate) target files are symlinks". But the ultimate target of a symlink cannot be a symlink, otherwise we can still follow the link and it is not the ultimate target. Since there is no such symlinks, we can define them as something else, i.e. broken symlinks. – weakish Apr 08 '16 at 04:57
  • 2
    @weakish I would rather say that -xtype follows the chain of symbolic links and evaluates the file at the end, which can only be a symbolic link in case it is broken. – Joó Ádám Apr 13 '16 at 16:40
  • 2
    @JoóÁdám "which can only be a symbolic link in case it is broken". Give "broken symbolic link" or "non exist file" an individual type, instead of overloading l, is less confusing to me. – weakish Apr 22 '16 at 12:19
  • 4
    The warning at the end is useful, but note that this does not apply to the -L hack but rather to (blindly) removing broken symlinks in general. – Alois Mahdal Jul 15 '16 at 00:22
  • I somehow suspected that -L was risky and that's why I found this. On breaking systems from symlinks I recall doing something (but I was able to salvage it because it was an experiment of sort) with /dev/null years ago. I needn't elaborate on why there was a symlink to it - it was in any case risky; it was not to do with find but something about recursively following the link nonetheless (or rather in this case dereferencing the link). I have this memory I've played with the same functionality before but I don't know for sure and it's not really important. (1/2) – Pryftan Oct 01 '19 at 14:27
  • As for -xtype l some comments on the options that change how symlinks in find go in order to find broken symlinks: (1) Because of how -L works don't use that option for broken symlink tests. (2) -P is the default and -xtype l works fine to find broken symlinks. But (3) -H appears to work but I do not know if it has any implications in different circumstances. Thus because what you and the commentator suggest it seems what needs to be done is only use -xtype l. Oh and correct: -xtype is not specified in POSIX. (2/2) – Pryftan Oct 01 '19 at 14:30
  • Another comment. Because the -xtype isn't available on all systems e.g. macOS the second option, find . -type l ! -exec test -e {} \; -print seems to me to be the proper answer. However - and maybe this is an artefact of books of old that I read aeons ago - I think it should actually be: find . -type l \! -exec test -e '{}' \; -print. However if you want to act on each file maybe -exec is a better idea? Or if you have GNU find then -print0 | xargs -0 more so. – Pryftan Oct 01 '19 at 14:42
  • @qwertzguy Did you mean to replace {} with "$(readlink /proc/XXX/exe)" as in find . -type l ! -exec test -e "$(readlink {})" \; -print ? – Setaa Aug 27 '20 at 00:24
  • @Setaa I don't quite remember (been 5 years), but yea I think I was just giving an example with just test, to adapt the full find command above, what you wrote seems correct. – qwertzguy Aug 27 '20 at 21:51
  • Comparing times of find . -xdev -type l -xtype l vs. find . -xdev -xtype l, I've found that the first test runs about 5 times faster than the last, while giving same results. – AnrDaemon Jan 15 '24 at 15:05
57

As rozcietrzewiacz has already commented, find -L can have unexpected consequence of expanding the search into symlinked directories, so isn't the optimal approach. What no one has mentioned yet is that

find /path/to/search -xtype l

is the more concise, and logically identical command to

find /path/to/search -type l -xtype l

None of the solutions presented so far will detect cyclic symlinks, which is another type of breakage. this question addresses portability. To summarize, the portable way to find broken symbolic links, including cyclic links, is:

find /path/to/search -type l -exec test ! -e {} \; -print

For more details, see this question or ynform.org. Of course, the definitive source for all this is the findutils documentaton.

pooryorick
  • 707
  • 5
  • 3
  • 2
    Short, consice, and addresses the find -L pitfall as well as cyclical links. +1 – Flimm Oct 07 '14 at 13:00
  • 3
    Nice. The last one works on MacOSX as well, while @rozcietrzewiacz's answer didn't. – neu242 Aug 01 '16 at 10:03
  • 2
    @neu242 That is likely because -xtype is not specified in POSIX and indeed if you look at find(1) in macOS it has -type but not -xtype. – Pryftan Oct 01 '19 at 14:35
  • find $HOME -type l -exec test ! -e {} \; -print prints a lot of 'permission denied' messages, namely from the Waydroid config folder. Fedora 37. – user598527 Mar 20 '23 at 15:01
  • find /home/user -type l -exec test ! -e {} \; -print | less also outputs the (open profile) lock files for all Firefox, Thunderbird and Tor browser. Should be left untouched, I can only assume. – user598527 Mar 20 '23 at 15:06
  • It is giving identical results, but it is not the same in terms of performance. In my tests, it is 2-5 times slower, than if you DO specify -type l first. – AnrDaemon Jan 15 '24 at 15:16
56

The symlinks command from http://www.ibiblio.org/pub/Linux/utils/file/symlinks-1.4.tar.gz can be used to identify symlinks with a variety of characteristics. For instance:

$ rm a
$ ln -s a b
$ symlinks .
dangling: /tmp/b -> a
Sam Morris
  • 1,012
9

If you need a different behavior whether the link is broken or cyclic you can also use %Y with find:

$ touch a
$ ln -s a b  # link to existing target
$ ln -s c d  # link to non-existing target
$ ln -s e e  # link to itself
$ find . -type l -exec test ! -e {} \; -printf '%Y %p\n' \
   | while read type link; do
         case "$type" in
         N) echo "do something with broken link $link" ;;
         L) echo "do something with cyclic link $link" ;;
         esac
      done
do something with broken link ./d
do something with cyclic link ./e

This example is copied from this post (site deleted).

Reference

DocSalvager
  • 2,152
andy
  • 99
  • Yet another shorthand for those whose find command does not support xtype can be derived from this: find . type l -printf "%Y %p\n" | grep -w '^N'. As andy beat me to it with the same (basic) idea in his script, I was reluctant to write it as separate answer. :) – syntaxerror Jun 25 '15 at 00:28
8

I believe adding the -L flag to your command will allow you do get rid of the grep:

$ find -L . -type l

http://www.commandlinefu.com/commands/view/8260/find-broken-symlinks

from the manual:

-L

Cause the file information and file type (see stat(2)) returned for each symbolic link to be those of the file referenced by the link, not the link itself. If the referenced file does not exist, the file information and type will be for the link itself.

Toby Speight
  • 8,678
kwarrick
  • 3,128
  • 1
  • 16
  • 10
7

For zsh users:

rm -v -- **/*(-@)

To also remove hidden ones:

rm -v -- **/*(D-@)

from zsh user guide (search for broken symlinks)

murla
  • 71
  • 1
  • 4
3

Simple no-brainer answer, which is a variation on OP's version. Sometimes, you just want something easy to type or remember:

find . | xargs file | grep -i "broken symbolic link"

Or, if you need to handle NULL terminators:

find . -print0 | xargs -0 file | grep -i "broken symbolic link"
fpmurphy
  • 4,636
trinth
  • 131
3

find . -xtype l will skip broken links pointing to files in inaccessible directories. You can just search for links to inaccessible files:

find . -type l ! -readable

This works correctly for cyclic symlinks and is also more efficient than using -exec test … with find command.

ArturZ
  • 31
2

I use this for my case and it works quite well, as I know the directory to look for broken symlinks:

find -L $path -maxdepth 1 -type l

and my folder does include a link to /usr/share but it doesn't traverse it. Cross-device links and those that are valid for chroots, etc. are still a pitfall but for my use case it's sufficient.

Iskren
  • 199
1

This will print out the names of broken symlinks in the current directory.

for l in $(find . -type l); do cd $(dirname $l); if [ ! -e "$(readlink $(basename $l))" ]; then echo $l; fi; cd - > /dev/null; done

Works in Bash. Don't know about other shells.

1

find -L . -type l |xargs symlinks will give you info whether the link exists or not on a per foundfile basis.

0

Most comprehensive (imho) command to find broken symlinks without crossing partition bounds. Note, that symlink arguments must be containing directories, not symlinks themselves.

find . -xdev -type d \
| stdbuf -oL xargs -d '\n' symlinks \
| stdbuf -oL grep -e '^dangling'
0

I took

find ./ -type l -exec file {} \; | that.awk

and piped it into an Awk script, that way you can do whatever.

#!/bin/awk -f
# ==============================================================================
BEGIN {
      FS=":"
      w=7
}
# ==============================================================================
{
   sub(/^ /,"",$2)               # remove leading spc
   p=index($2,"to")              # 'to' pointer
   s=substr($2,1,p-2)            # status
   t=substr($2,p+3)              # target

switch(s) { case /^broken.*/ : printf("\n") printf("%"w"s %s\n","target:",t) printf("%"w"s %s\n","source:",$1) printf("%"w"s %s\n","status:",s) break

default:

printf("\n")

printf("%"w"s %s\n","target:",t)

} }

==============================================================================

END { }

==============================================================================

functions

==============================================================================

Toby Speight
  • 8,678
liz
  • 21