64

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:

[root@trees ~]# 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?

5 Answers 5

62

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. Assuming ln is from GNU coreutils 8.22, just pass the -r option to ln in addition to the other options and it will convert the target to a relative path. For older systems or for bulk rewriting of valid links, 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.

13
  • No offense, this is a great one-liner, but Bash's string substitution could probably do some magic w.r.t. the path change and would be easier. Feb 8, 2012 at 20:30
  • 1
    @STATUS_ACCESS_DENIED How so? The only string operation is to prepend /mnt to a path; you need no fancier string operation than concatenation. Feb 8, 2012 at 20:41
  • @Gilles: sorry, I was thinking more about your remark with the relative paths. For an exact "translation" of your example you are of course right. Feb 8, 2012 at 20:55
  • 1
    @GabrielStaples ln -r didn't exist yet when I wrote this answer (added in GNU coreutils 8.16 released in March 2012). I don't see why ln -snfr wouldn't work: I tried on an absolute link and it did convert it to relative. There was a bug that this didn't work originally, but it was fixed in coreutils 8.22 released in December 2013. Jan 17 at 21:01
  • 1
    @GabrielStaples Ah, I see. Your test command is missing the -n option to ln (which you correctly included in your original comment here). Jan 19 at 11:00
17

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.

3
  • 1
    I find this solution better because it uses a string replace, which helps in cases where you have to change the name of a folder in the middle of the path. The solution is also quite easy to modify to perform more complex transformations if required.
    – Gallaecio
    Nov 29, 2014 at 22:56
  • I'd recommend quoting the arguments to the string substitution, as that must be done to use slashes, e.g. for the path in the OP's question. target=${target/"/home"/"/mnt/home"} Very helpful, though. Thanks. Nov 29, 2018 at 18:08
  • This helped me as well. I'd love to see it take three arguments, link, original text, and replacement text so that the replacement isn't hard coded. Nov 10, 2021 at 4:13
5

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

1
  • 3
    Bind-mounting often tends to be less fragile than symlinks in scenarios where programs are aware of symlinks and act differently depending on the fact ... Feb 8, 2012 at 20:27
3

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.

5
  • 1
    I can't remember what specifically I was doing here over 9 years ago when I asked this question... I do vaguely recall then not knowing that there was a utility which went from absolute to relative symlinks and may have taken that approach? I am sure I did use a, b, etc as a mans to obsfucate actual filenames which may have been client data. Agree I could have used more realistic filenames, though the result should be the same.
    – Josh
    Oct 19, 2020 at 21:26
  • 1
    @Josh glad to see you are still active here after 9 years! Gilles is my hero on this site, so I didn't want to diminish his reply, but I think the rename command is very useful in situations like this one. It's also my go-to tool for general file renaming. (I use both the CLI version and krename in KDE for that.) The only issue I have with the rename command is that the man page isn't very good IMO. That's why I gave a little more info in my answer.
    – MountainX
    Oct 19, 2020 at 21:59
  • Not nearly as active as I was, but I'm around! And thanks for your answer, I did not now about this tool but will keep it in mind should I have another need like... whatever my need was 9 years ago :)
    – Josh
    Oct 19, 2020 at 22:01
  • Note that the rename package found in the standard Debian repositories is different than the package under discussion above...at least as of Bullseye
    – Digger
    May 26 at 19:17
  • To followup on what @Digger said unfortunately the two common (and both useful) rename commands are disambiguated differently on different distros. The util-linux rename referenced here is installed as rename.ul on some versions of ubuntu and debian. However recently they've removed it entirely and you can only get it by building from source yourself.
    – EricS
    Aug 15 at 20:00
1

I wrote a fix_broken_symlinks bash script tool:

I'm hoping it will become a companion tool to the popular symlinks, which does some other useful things.

Here is my tool:

# find all broken symlinks
fix_broken_symlinks path/to/dir

# dry-run fix them
fix_broken_symlinks path/to/dir find_regex replacement_str

# actually fix them (passing `-f` to force it)
fix_broken_symlinks path/to/dir find_regex replacement_str -f

I put a lot of effort into getting really pretty and aligned output.

Example run:

/home/gabriel/GS/dev/RESEARCH$ fix_broken_symlinks . '/home/gabriel/GS/dev' '/home/gabriel/GS/dev/RESEARCH' -f
4 broken symlinks found!

************************************************************************
'--force' is on! Writing new **relative** symlinks!
************************************************************************

    1/4:    BROKEN: './lldb/Link to Debuggers_General' -> '/home/gabriel/GS/dev/Debuggers_General'
            FIXED : './lldb/Link to Debuggers_General' -> '/home/gabriel/GS/dev/RESEARCH/Debuggers_General'
            FINAL : './lldb/Link to Debuggers_General' -> '../Debuggers_General'
    2/4:    BROKEN: './Link to notes - Gabriel.odt' -> '/home/gabriel/GS/dev/notes - Gabriel.odt'
            FIXED : './Link to notes - Gabriel.odt' -> '/home/gabriel/GS/dev/RESEARCH/notes - Gabriel.odt'
            FINAL : './Link to notes - Gabriel.odt' -> 'notes - Gabriel.odt'
    3/4:    BROKEN: './books/Link to 104The Pragmatic Programmer, From Journeyman To Master - Andrew Hunt, David Thomas - Addison Wesley - 1999.pdf-1.desktop' -> '/home/gabriel/GS/dev/Design_Docs/104The Pragmatic Programmer, From Journeyman To Master - Andrew Hunt, David Thomas - Addison Wesley - 1999.pdf-1.desktop'
            FIXED : './books/Link to 104The Pragmatic Programmer, From Journeyman To Master - Andrew Hunt, David Thomas - Addison Wesley - 1999.pdf-1.desktop' -> '/home/gabriel/GS/dev/RESEARCH/Design_Docs/104The Pragmatic Programmer, From Journeyman To Master - Andrew Hunt, David Thomas - Addison Wesley - 1999.pdf-1.desktop'
            FINAL : './books/Link to 104The Pragmatic Programmer, From Journeyman To Master - Andrew Hunt, David Thomas - Addison Wesley - 1999.pdf-1.desktop' -> '../Design_Docs/104The Pragmatic Programmer, From Journeyman To Master - Andrew Hunt, David Thomas - Addison Wesley - 1999.pdf-1.desktop'
    4/4:    BROKEN: './books/TDD--James Grenning Test-Driven Dev for Embedded C/pragprog.com/Link to TDD_code' -> '/home/gabriel/GS/dev/books/TDD_code'
            FIXED : './books/TDD--James Grenning Test-Driven Dev for Embedded C/pragprog.com/Link to TDD_code' -> '/home/gabriel/GS/dev/RESEARCH/books/TDD_code'
            FINAL : './books/TDD--James Grenning Test-Driven Dev for Embedded C/pragprog.com/Link to TDD_code' -> '../../TDD_code'

************************************************************************
'--force' is on! New **relative** symlinks written!
************************************************************************

Run 'fix_broken_symlinks` "."' one more time to check if any symlinks are
still broken.

real    0m0.157s
user    0m0.101s
sys 0m0.086s

Details

I just wrote a tool I used to fix nearly 100 broken symlinks. It's called fix_broken_symlinks.sh, and it's part of my eRCaGuy_dotfiles repo.

Here's how to get it:

# download it and make it executable
wget https://raw.githubusercontent.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/master/useful_scripts/fix_broken_symlinks.sh
chmod +x fix_broken_symlinks

# help menu, with tons of info., & examples
./fix_broken_symlinks -h

General usage & examples:

# General usage
fix_broken_symlinks path/to/dir find_regex replacement_str

# (Dry run) fix all broken symlinks in the current dir (.) by replacing their
# target paths with John's home dir instead of Gabriel's.
fix_broken_symlinks . '/home/gabriel/some/dir' 'home/john/some/dir'

# Pass `-f` to force it to actually apply the changes
fix_broken_symlinks . '/home/gabriel/some/dir' 'home/john/some/dir' -f

From the help menu:

'fix_broken_symlinks' version 0.1.0

USAGE
    fix_broken_symlinks [options] <dir> [<find_regex> <replacement_string>]

DESCRIPTION

    Find all broken symlinks in directory 'dir', then extract the target paths they point to, search
    those paths for 'find_regex', and replace those findings with 'replacement_string'. Finally,
    recreate the symlinks as relative symlinks with those replacements to the target paths in
    place.

    If the 'find_regex' and 'replacement_string' are not provided, it will simply find and print out
    the broken symlinks as they currently are.

    All runs are dry-runs unless you use '--force'.

OPTIONS
    -h, -?, --help
        Print help menu
    -v, --version
        Print version information.
    --run_tests
        Run unit tests.
    -d, --debug
        Turn on debug prints.
    -f, --force
        Actually do the symlink replacements. With*out* this option, all runs are dry runs.

EXAMPLE USAGES:

    fix_broken_symlinks -h
        Print help menu.
    fix_broken_symlinks --help
        Print help menu.
    fix_broken_symlinks .
        Recursively find all broken symlinks in the current directory (.).
    fix_broken_symlinks . '/home/gabriel/some/dir' 'home/john/some/dir'
        (Dry run) fix all broken symlinks in the current dir (.) by replacing their target paths
        with John's home dir instead of Gabriel's.
    fix_broken_symlinks . '/home/gabriel/some/dir' 'home/john/some/dir' -f
        (Real run) fix all broken symlinks in the current dir (.) by replacing their target paths
        with John's home dir instead of Gabriel's. **Relative** symlinks will be created in the end.

Other tools I use are:

  1. Manually fix one-off broken symlinks with the following cmds:

    Note: -n is required if the symlink you're about to replace is to a directory. See the discussion beginning with my comment here for details: How can I "relink" a lot of broken symlinks?

    # NB: **All** of these options are important, and what I want. See 'man ln' 
    # for what each does.
    ln -svnri target_path symlink_path
    
  2. Convert all symlinks from absolute to relative with:

    symlinks -rsvt .  # dry run
    symlinks -rsvc .  # the real replacement
    

    Install symlinks in Ubuntu with:

    sudo add-apt-repository universe
    sudo apt update
    sudo apt install symlinks
    

    See: https://www.makeuseof.com/how-to-find-and-fix-broken-symlinks-in-linux/

For more of my notes

See:

  1. fix_broken_symlinks -h
  2. My notes in this document here: eRCaGuy_dotfiles/git & Linux cmds, help, tips & tricks - Gabriel.txt. Search it for = Symlink Utilities: =.

You must log in to answer this question.

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