I have a bunch of symlinks in /home to files and sub-directories in /foo. I want to target the new directory, /bar.

My approach was to look at all invalid symlinks and verify that they were pointing to /foo. I then did the following:

sudo find . -type l -! -exec test -e {} \; -exec sh -c '\
   old_link_target=$(readlink "$0"); \
   new_link_target=${old_link_target//foo/bar}; \
   ln -snf $new_link_target $0' {} \;

However, I want a more precise approach that would not include the initial step of putting eyes on the invalid symlinks. So, for the sake of this question, assume /foo still exists so another approach is required.



find . -lname '/foo*' -printf '%p\0%l\0' |
  awk -vRS='\0' '
      getline target
      sub("^/foo", "/bar", target)
      printf("%s\0%s\0", target, $0)
    }' |
  xargs -r0n2 ln -sfT

Or with recent GNU sed:

find . -lname '/foo*' -printf '%l\0%p\0' |
  sed -z 's|^/foo|/bar|;n' |
  xargs -r0n2 ln -sfT

Beware that you will potentially be affecting the ownership of the symlinks (so for instance, their original author won't be able to remove them any longer if they're in a directory they don't own but have write access to and has the t bit set (like /tmp)).

To prevent that, you could use GNU tar instead:

find . -lname '/foo*' -print0 |
  tar --null -T - -cf - --transform='s@^/foo@/bar@' |
  tar xpf -

Here's a tiny test set:

mkdir ./tmp1 ./tmp2    
touch ./tmp1/file1 ./tmp2/file2
ln -s ./tmp1 ./tmp3
ls -H ./tmp3



So now I'll...

mount --bind ./tmp2 ./tmp1
ls -H ./tmp3



tada! Now all links that once pointed to ./tmp1 will automatically point to ./tmp2 - as, in fact, so does ./tmp1 because they are the same mountpoint. Had ./tmp2 contained child-mounts I might have done:

mount --rbind ./tmp2 ./tmp1

And recursively --bind mounted the trees. And that doesn't even the scratch the surface on the Linux kernel's vfs shared subtrees mount options.

On a Linux system you don't have to concern yourself overmuch with what directory a file points to because you can atomically affect that file's location using the kernel's virtual filesystem mount tree - it is at your disposal. And you can manage the entire thing in /etc/fstab.

>>/etc/fstab \
printf '%s %s none bind,defaults 0 0\n' \
    "/bar" "/foo"

And your problems are solved.

  • +1 This may or may not apply to a particular situation (if distributing files across many computers, it's much easier to fix it once rather than on every machine; you may not have permission to mount; the current target may still need to exist and be accessible), but it's a good strategy to consider if you find yourself trying to change symlink targets to fix a problem. – Xiong Chiamiov Jul 12 '16 at 19:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.