10

I frequently move directory trees to other locations or copy their tarballs to other machines, and I would like to have a method to check whether any symlinks in a directory tree A point to locations outside of A since these will be broken in the moved / copied directory.

Marcus Junius Brutus
  • 4,427
  • 11
  • 43
  • 63

5 Answers5

7

You want a program called realpath, used in conjunction with find.

E.g.:

find . -type l -exec realpath {} \; | grep -v "^$(pwd)"
bahamat
  • 38,658
  • 4
  • 70
  • 103
  • 1
    The incantation you provide only reports the offending target location, not the symlink that points to it. As such I have removed my acceptance. Please see my "answer" below: http://unix.stackexchange.com/a/308899/24044 and if you are happy with it (or improve it) I will delete my "answer" and accept your question again. – Marcus Junius Brutus Sep 09 '16 at 16:09
2

I had to tweak a little the answer given by @bahamat to make it work.

The version provided simply reported the offending absolute location but not the symlink that points to it.

Here's what I used (I am sure it can be improved):

for f in $(find . -type l ); do echo -n $(realpath $f) && echo -n "|" && echo $f ; done | grep -v "^$(pwd)" | cut -d \| -f 2 
Marcus Junius Brutus
  • 4,427
  • 11
  • 43
  • 63
  • I found this to be the most useful script: `for f in $(find . -type l); do echo $(realpath -m -q $f) '<-' $f; done | grep-v "^$(pwd)"` (most notably `-m` and `-q` which filters out broken and non-external links) – Adam Lindberg Jan 19 '17 at 13:50
2

With zsh:

cd -P -- "$dir"
for i (**/*(ND@)) [[ $i:A = $PWD/* ]] || [[ $i:A = $PWD ]] || print -r -- "$i => $i:A"

Now, if the directory is /foo and you have /foo/bar that's a symlink to /foo/baz, that's a link whose target is in /foo, but once moved, the link will still be broken, so you may want also to match symlinks to absolute paths.

But even then, a bar => ../foo/baz in /foo would be an issue (false negative), so would a a => b where b is a symlink outside the tree (false positive, depending on how you want to look at it)

Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
2

Use bindfs to create another view of that directory tree.

mkdir /tmp/view
bindfs /some/directory /tmp/view

Then use the symlinks utility (shipped by many distributions, or compile it from source) to detect cross-filesystem links.

symlinks -r /tmp/view | sed -n 's/^\(absolute\|other_fs\): //p'

(Note that parsing the output assumes that your symbolic links and their targets do not contain newlines, nor do paths to symbolic links contain the substring  -> .) That same utility can also convert absolute symlinks to relative (but you'd want to do that from the original location).

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
1

GNU coreutils provedes realpath, which resolves symlinks. With this, you could compare each symlink's target to the current working directory with something like:

#!/bin/bash

find . | while read filename
do
  if realpath $filename | grep -E "^$PWD" > /dev/null
  then
    echo 'this file is safe'
  else
    echo 'this file links externally'
  fi
done
colons
  • 231
  • 1
  • 6
  • Several problems: no `-type l`, no `-r` option to `read`, IFS not sanitized for `read`, `$filename` not quoted, `$PWD` treated as a regular expression, paths with newline characters not accounted for, `/foobar` would be matched for `$PWD` == "/foo" – Stéphane Chazelas Oct 04 '12 at 21:38