How can I “relink” a lot of broken symlinks?

I have a directory tree which has a bunch of symbolic links to files under /home… however, I have moved /home to /mnt/home and need a way to “relink” all of the symlinks. Does such functionality exist or do I need to write a script to do so?

As an example, I have something like the following:

[<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aedcc1c1daeedadccbcbdd">[email protected]</a> ~]# ls -l /mnt/home/someone/something
total 4264
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 a -> /home/someone/someotherthing/a
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 b -> /home/someone/someotherthing/b
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 c -> /home/someone/someotherthing/c
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 d -> /home/someone/someotherthing/d
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 e -> /home/someone/someotherthing/e

/mnt/home/someone/something/subdir:
total 4264
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 a -> /home/someone/someotherthing/subdir/a
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 b -> /home/someone/someotherthing/subdir/b
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 c -> /home/someone/someotherthing/subdir/c
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 d -> /home/someone/someotherthing/subdir/d
lrwxrwxrwx 1 jnet www-data      55 2011-08-07 13:50 e -> /home/someone/someotherthing/subdir/e

I want a command which will find all the symlinks and relink to the same places but underneath /mnt/home instead of /home

Does such a command exist?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

There is no command to retarget a symbolic link, all you can do is remove it and create another one. Assuming you have GNU utilities (e.g. under non-embedded Linux or Cygwin), you can use the -lname primary of find to match symbolic links by their target, and readlink to read the contents of the link. Untested:

find /mnt/home/someone/something -lname '/home/someone/*' 
     -exec sh -c 'ln -snf "/mnt$(readlink "$1")" "$1"' sh {} ;

It would be better to make these symbolic links relative. There’s a convenient little utility called symlinks (originally by Mark Lord, now maintained by J. Brandt Buckley), present in many Linux distributions.

Before the move, or after you’ve restored valid links as above, run symlinks -c /mnt/home/someone/something to convert all absolute symlinks under the specified directory to relative symlinks unless they cross a filesystem boundary.

Method 2

I know this is not exactly what the author is requesting but it seems they already have their answer so I’m adding this for others like me who stumble upon the question.

The following should help if a more flexible solution is required such as having a bunch of broken symbolic links which can be fixed by replacing part of the symbolic link’s targets.

eg. After a change of username, to replace the old username with the new username in the target of many links, after the move had already been done. Create a script called replace-simlinks shown below:

#!/bin/bash
link=$1
# grab the target of the old link
target=$(readlink -- "$1")

# replace the first occurrence of oldusername with newusername in the target string
target=${target/oldusername/newusername}

# Test the link creation
echo ln -s -- "$target" "$link"

# If the above echo shows the correct commands are being issued, then uncomment the following lines and run the command again
#rm "$link"
#ln -s "$target" "$link"

and call it with the following command:

find /home/newusername/ -lname '/home/oldusername/*' -exec ~/bin/replace-simlinks {} ;

Hope this help somebody

edit: Thanks Gilles for the kickstart on this script and the tip about using the symlinks script to make the links relative.

Method 3

Create /home as a symlink to /mnt/home, and all the existing symlinks will be valid again.

Method 4

One of the otherwise great answers states:

There is no command to retarget a symbolic link, all you can do is remove it and create another one.

The rename command’s -s option will “rename a symbolic link target, not the symbolic links itself”. It will also operate on multiple symbolic links at once. This seems to be the behavior the OP requested.

The rename command is part of the util-linux package and is available from https://www.kernel.org/pub/linux/utils/util-linux/. It has been available since at least June 2011.

OP said:

I want a command which will find all the symlinks and relink to the same places but underneath /mnt/home instead of /home

The rename command can do that and it can do it for multiple files (symbolic links) at once. Unfortunately, the OP uses a, b, c, d, e as example filenames. Those names don’t seem realistic, so I won’t use them in my answer. (If the OP is working with multiple subdirectories the solution may be more complex than what I’m presenting, but I still think rename is a very convenient tool for this general requirement, and it can be combined with other tools such as find ... -execdir ....)

I did just use rename to fix multiple broken symbolic links as shown below. This is the “before” listing from ls:

second.files -> first.files.tar.zst
second.files.sig -> first.files.tar.zst.sig

The targets first.files.tar.zst* do not exist. This is analogous to the OP’s situation.

I used this command to fix all the broken symbolic links:

rename -s 'first' 'second' second.files*

The two single-quoted arguments apply to the target. The last argument, second.files* selects the links upon which to operate. Those symbolic links are not renamed. Due to the use of the -s option, the search/replace operation is performed on the targets.

Here is the “after” listing from ls after running the above rename command

second.files -> second.files.tar.zst
second.files.sig -> second.files.tar.zst.sig

The links have been “relinked” and they work correctly now.

Here is an example of using it to relink any symbolic links that has the pattern “somelinks*” in its name where we want to change the target location from /home to /mnt/home.

rename -s '/home' '/mnt/home' somelinks*

The man page suggests that the single quotes are mandatory. In this case, multiple broken links matching the pattern somelinks* were fixed, which means they were retargeted to point to the correct location. The symbolic links name itself was not changed, but the file it pointed to was changed.

It would be better to use relative symbolic links.

I’m sure the person who wrote the above statement had good justification. However, we actually prefer absolute symbolic links in our environment (where we utilize network shares). If the directories on the clients and fileserver are laid out consistently, absolute symbolic links will work across file systems and even over network shares. Within our team, they provide for a very consistent and robust navigation solution. We can all share consistent file paths with each other. Haven’t had problems with this approach over the last 8 or so years. But if your situation is different, by all means consider relative symlinks as Gilles suggested.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x