42

On this question or on this one (for example) you will get solutions on how to look for symlinks pointing to a given directory (let's call it /dir1), while I am interested to symbolic links possibly pointing to any file/folder inside /dir1.

I want to delete such directory but I am not sure that I am safe to do so, as on an other directory (let's call it /dir2), I may have symlinks pointing to inner parts of /dir1.

Further, I may have created these symlinks using absolute or relative paths. My only help is that I know the symlinks I want to check are on a mounted filesystem, on /dir2.

Antonello
  • 1,023

4 Answers4

38

You can find all the symbolic links using:

find / -type l 

you might want to run this as root in order to get to every place on the disc.

You can expand these using readlink -f to get the full path of the link and you should be able to grep the output against the target directory that you are considering for deletion:

find / -type l -exec readlink -f {} + | grep -F /dir2

Using find / -type l -printf '%l\n' doesn't work as you get relative links like ../tmp/xyz which might be pointing to your target dir, but are not matched because they are not fully expanded.

Anthon
  • 79,293
  • 2
    In case of subtree it can be useful to follow symlinks: find -L /subtree -xtype l -exec readlink -f {} + – ruvim Nov 30 '17 at 11:52
  • 1
    What does the + do here? – Liam Jul 21 '20 at 15:24
  • 2
    @Liam It tells find that the -exec is finished and that multiple arguments should be passed to the command (readlink) at the position of {} (you can also finish with ; (which needs to be escaped in many shells), to invoke the command with a single argument, which is much slower to execute. – Anthon Jul 21 '20 at 20:03
23

In my case, the accepted answer wasn't useful (because it didn't output the link source). Here is what worked for me.

I worked around it using two -exec clauses:

find /home/ -type l -exec readlink -nf {} ';' -exec echo " <- {}" ';' | grep "/dir2"
  • 4
    This works, but the arrow (->) is misleading, it should point from right to left (<-). – Attila Csipak Oct 05 '20 at 13:54
  • 3
    Rather than flipping the arrow, this would print the expected output: find /home/ -type l -printf '%p -> ' -exec readlink -f {} ';' | grep "/dir2" – MichaelK Feb 23 '21 at 20:56
4

With zsh:

print -rC1 /dir2/**/*(ND@e['[[ $REPLY:P = /dir1(/*|) ]]'])

Broken down:

  • print -rC1: prints its arguments raw on 1 Column. Here, the arguments will be those generated from the following glob. Replace with ls -ld to get more information about each symlink¹
  • **/: any level of subdirectories (recursive globbing)
  • *: file with any name (made of any number of characters, though zsh's * like that of most shells will also allow non-characters).
  • (...): glob qualifiers to further qualify the matching on other criteria than just the name of the files
  • N: enable nullglob for that one glob (won't fail if there's no match, just pass an empty list to print which will print nothing).
  • D: enable dotglob: also consider hidden files.
  • @: restrict to files of type symlink.
  • e['code']: select files for which the code evaluates to true. Inside the code, the file being considered is stored in $REPLY.
  • $REPLY:P: gets the absolute and canonical (symlink free) path to the file (similar to what the realpath() standard function does).
  • [[ string = pattern ]] returns true if the string matches the pattern (from ksh).
  • /dir1(/*|) as a pattern matches on /dir1 alone or /dir1/ followed by anything.

¹ with the caveat that if there's not matching file, that will list the current working directory. With ls, it would be better to remove the N glob qualifier

-2

Depending on your circumstances, you could delete the directory, then delete any resultant invalid symlinks with the following:

find -xtype l -delete

The xtype test returns 'l' if the symlink is broken.