4

I like to use relative symlinks when possible because they stay valid even when my filesystem is mounted in a non-root location, for example if I am accessing it remotely.

However, if I move a directory which contains relative symlinks, in general those links become invalid.

I am looking for a command that I can use to move a whole directory (tree), rewriting only those relative symlinks that point outside the directory so that they still point to the same files.

There is a related question on Stack Overflow, How do I move a relative symbolic link?. Also, here on Unix & Linux, find all symbolic links in a directory tree pointing outside that tree, and (referring to a much harder problem) is there some kind of symbolic links that support moving or renaming the target file?.

For example, a command called mv-rel which behaves as follows (the other commands being their GNU coreutils versions):

$ mkdir foo
$ touch bar
$ ln -sr bar foo/
$ touch foo/flob
$ ln -sr foo/flob foo/qux
$ ls -gG foo/
total 0
lrwxrwxrwx 1 6 Dec  1 00:05 bar -> ../bar
-rw-r--r-- 1 0 Dec  1 00:24 flob
lrwxrwxrwx 1 4 Dec  1 00:24 qux -> flob

$ mkdir baz
$ mv-rel foo/ baz
$ ls -gG baz/foo/
total 0
lrwxrwxrwx 1 6 Dec  1 00:05 bar -> ../../bar
-rw-r--r-- 1 0 Dec  1 00:24 flob
lrwxrwxrwx 1 4 Dec  1 00:24 qux -> flob

Unless I'm missing something, this also seems like it would be a useful command-line option to the 'mv' command (maybe also 'cp'). But for the purposes of this question I am looking for something that exists now, or a simple portable script that implements this functionality.

3
  • Check if rsync has a symlink handling option that works for you. Commented Dec 1, 2018 at 9:42
  • Not seeing it... Commented Dec 1, 2018 at 12:42
  • Have you tried a gui file manager or does it have to be a command?
    – rhubarbdog
    Commented Dec 3, 2018 at 2:47

2 Answers 2

2

I finally wrote a solution and posted it on Github:

https://github.com/minTaqa/mvrel

It is not currently as flexible as "mv", in that only two arguments are allowed, and the second argument must be a directory. However, I found it useful for moving something that had a lot of relative symlinks. I wanted to make sure that it could deal with spaces, newlines, and other riffraff in filenames, so this was also a chance for me to learn about Bats, the TAP-compliant testing framework written in Bash, where I put some unit tests to convince myself that everything is working.

The script itself is also written in Bash. I thought this would be easiest and most portable language for the task, but I ended up having to work around things like the fact that command substitution deletes trailing newlines (a problem if you have filenames with trailing newlines, which is mostly theoretical but still annoying). Then there is find ... -print0 and IFS= read -rd '', plus lots of quotes and "--" arguments -- stuff that wouldn't be needed in a slightly lower-level scripting language like Perl. I also wouldn't be doing string manipulation by parameter expansion in Perl. Anyway you can rewrite it in Perl or even C if you like; as it is, it is reasonably short in Bash.

0

Here's a fairly destructive method: replace the relative links with absolute links:

cd foo
for f in *; do
    if [[ -h $f ]]    # True if file is a symbolic link.
    then
        abs=$(readlink -f "$f") && rm "$f" && ln -s "$abs" "$f"
    fi
done

You must log in to answer this question.

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