How to find symbolic links where target path matches a pattern

findsymlink

I am trying to find all symlinks below current folder whose target contains 'Dropbox'

My best guess was:

find . -type l -iname '*Dropbox*'

But this returns nothing.

I want to find items such as:

com.adobe.illustrator.plist -> /Volumes/data/Dropbox (Personal)/Mackup/Library/Preferences/com.adobe.illustrator.plist
com.apple.Terminal.plist -> /Volumes/data/Dropbox (Personal)/Mackup/Library/Preferences/com.apple.Terminal.plist
com.noodlesoft.Hazel.plist -> /Volumes/data/Dropbox (Personal)/Mackup/Library/Preferences/com.noodlesoft.Hazel.plist

so that I can re-create the links without the (Personal) text. (If there's a way to do that with one line, that would be excellent!)

Best Answer

If your find supports -lname or -ilname (GNU extensions also supported by some BSDs (including macOS which your /Volumes suggest you might be using)).

find . -lname '*Dropbox*' -exec ls -ld {} +

Or case-insensitively:

find . -ilname '*dropbox*' -exec ls -ld {} +

If not (or even if yes if you want to do anything easily with those links or prefer a sorted list) and you have zsh (shipped by default on macOS), you could do:

zmodload zsh/stat
set -o extendedglob # for (#i) for case insensitive
link_match() {
  local target
  zstat -A target +link -- "${2-$REPLY}" &&
    [[ $target = $~1 ]]
}
ls -ld -- **/*(D@e:'link_match "(#i)*dropbox*"':)

Or to recreate the links:

zmodload zsh/stat
for l (**/*(D@))
  zstat -A target +link -- $l &&
    [[ $target = *"Dropbox (Personal)"* ]] &&
    rm -f -- $l &&
    ln -s -- ${target//" (Personal)"} $l

Note that any shell code can always be written on one line if need be, but I fail to see the point. Here, just join those lines together if you want it on one line.

To do something similar with bash and GNU or FreeBSD/macOS utilities, it's more complicated and would be less efficient:

LC_ALL=C find . -lname '*Dropbox (Personal)*' -exec bash -c '
  for l do
    target=$(readlink "$l" && echo .) || continue
    target=${target%??}
    rm -f "$l" &&
      ln -s -- "${target//" (Personal)"}" "$l"
  done' bash {} +
Related Question