I have a directory where applications are installed and symlinks are updated to point to the latest version.

This leaves lots of old directories around that aren't used because there is no symlink pointing to it.

So far I have been using ls -l to check and manually remove them. It would be useful to be able to script this.

I can exclude the symlinks with

find -maxdepth 1 \! -type l -exec ls -dl {} +

However, I can't work out how to find out what they are pointing to and exclude those as well.

How can I list the directory excluding the symlinks and the directories they are pointing at?


2 Answers 2


With zsh, you could do:

ls -ld -- *(^@e['(($linktargets[(eI)$REPLY:P]))'])

Where we create a $linktargets array of realpath() applied to all symlinks, and then pass to ls -ld the files that are not symlinks and also whose realpath is not found in the array.

Another approach that doesn't involve getting the realpaths would be to build an array of all the non-symlink files, another one with the targets of the symlinks, and subtract the two:

zmodload zsh/stat
stat -A symlink_targets +link -- *(@)
ls -ld -- ${non_symlinks:|symlink_targets}

Though that assumes the symlink targets contain no / characters (links are link -> file, not link -> ./file, link -> /path/to/file...)


Assuming the GNU find and realpath, and no filenames with newlines:

find . -mindepth 1 -maxdepth 1 -exec realpath -e --relative-to=. {} + | sort | uniq -u

This works by asking realpath to resolve all the paths, making the files pointed to by symlinks appear twice in the list. sort | uniq -u will only keep those which appear just once.

Take care with symlinks which may point to outside the tree (or to subdirectories), which you may want to know about, but also filter out with grep -v / before prepending any | xargs rm -fr to that pipeline ;-).

A better (more standard, but less obvious) alternative to find . -mindepth 1 -maxdepth 1 ... would be find . ! -name . -prune ....

You can also support newlines in filenames by using the -z and -0 options of the GNU tools.

Also notice that the localized collate implementation is broken on many systems (completely ignoring some special characters), so you better disable any localization for sort and uniq.

Putting it all together:

find . ! -name . -prune -exec realpath -ez --relative-to=. {} + |
  grep -zv / |
  LC_ALL=C sort -z |
  LC_ALL=C uniq -zu |
  xargs -r0 do_something

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .