17

I have a web app that has a bunch of symbolic links in subdirectories throughout it. I need to move the app to another directory structure, and I need to update all the symlinks to point to the new path. For example:

Old Dir: /home/user/public_html/dev
New Dir: /home/user/public_html/qa
Old Symlink: /home/user/public_html/qa/multisites/slave01/images -> /home/user/public_html/dev/images
New Symlink: /home/user/public_html/qa/multisites/slave01/images -> /home/user/public_html/qa/images

The problem is that there's a lot of these scattered throughout various directories. How can I recursively search from the root and recreate all symlinks pointing to /dev/ with /qa/?

6 Answers 6

23

This bash command should do it for you:

find /home/user/public_html/qa/ -type l -lname '/home/user/public_html/dev/*' -printf 'ln -nsf "$(readlink "%p" | sed s/dev/qa/)" "$(echo "%p" | sed s/dev/qa/)"\n' > script.sh

It uses find to identify all files in the qa directory that are symbolic links with a target that's in the dev directory, and for each one, it prints out a bash command that will replace the link with a link to the equivalent path in qa/. After you run this, just execute the generated script with

bash script.sh

You might want to examine it manually first to make sure it worked.

Here's a more verbose version of the above find command for easier reading (though I wouldn't necessarily write it this way in practice):

SRC_DIR="/home/user/public_html/qa"
OLD_TARGET="/home/user/public_html/dev"
SUB="s/dev/qa/"

find $SRC_DIR -type l \
  -lname "$OLD_TARGET/*" -printf \
  'ln -nsf "$(readlink "%p"|sed $SUB)" "$(echo "%p"|sed $SUB)"\n'\
 > script.sh
13
  • This creates an empty script.sh. And running the find command as so: find /home/user/public_html/qa/ -type l -lname '/home/user/public_html/dev/*' doesn't output anything.
    – ggutenberg
    Commented Jun 29, 2010 at 4:25
  • You did remember to change the paths to the actual ones on your filesystem, right? What happens if you run just find /home/usr/public_html/qa/ -type l? If that doesn't find the links, something very weird is going on with your system.
    – David Z
    Commented Jun 29, 2010 at 5:34
  • Yes, "find /home/user/public_html/qa/ -type l" outputs the links. But adding the -lname parameter it doesn't output anything.
    – ggutenberg
    Commented Jun 30, 2010 at 3:34
  • Actually, on further testing it looks like this is working. Not sure what I was doing wrong yesterday, but seems ok now. Thanks.
    – ggutenberg
    Commented Jun 30, 2010 at 3:41
  • Huh, weird. Well, if you ever figure out what was going wrong, put a comment here. I'm curious.
    – David Z
    Commented Jun 30, 2010 at 3:54
6

In case anyone else finds this when searching for a solution: Create a file named "linkmod.sh" containing:

#!/bin/sh
PATTERN1=`echo "$2"`
PATTERN2=`echo "$3"`
LINKNAME=`echo "$1"`
OLDTARGET=`readlink "$1"`
NEWTARGET=`echo "$OLDTARGET" \
| sed -e 's/'"$PATTERN1"'/'"$PATTERN2"'/'`
echo ln -nsf "$NEWTARGET" "$LINKNAME"

and run

find . -type l -print0 | xargs -0IX echo linkmod.sh X "pattern1" "pattern2"

You can ofc use the -lname option in find if needed.

NOTE: you have to use 2x \ in the patterns before any characters that require \ in sed, since echo removes one. For example

find . -type l -print0 | xargs -0IX echo linkmod.sh X "folder\\ name\\/file" "folder2\\ name\\/file"

Remove the echo from the last line if the ln commands are correct.

1
  • Might be good to clarify that both the echo in the final line of the script and the echo in the find .. | xargs .. linkmod.sh ... command itself both need to be removed. Commented Aug 10, 2017 at 17:51
4

I created a bash script link_rename.sh for the recursively renaming symbolic links in a given directory

#! /bin/bash

DIR=$1
OLD_PATTERN=$2
NEW_PATTERN=$3

while read -r line
do
    echo $line
    CUR_LINK_PATH="$(readlink "$line")"
    NEW_LINK_PATH="$CUR_LINK_PATH"  
    NEW_LINK_PATH="${NEW_LINK_PATH/"$OLD_PATTERN"/"$NEW_PATTERN"}"
    rm "$line"
    ln -s "$NEW_LINK_PATH" "$line"
done <<< $(find "$DIR" -type l)

It can be executed as link_rename.sh /home/human/dir link1 link2

The script has 3 arguments:

  1. The directory in which you want to perform the batch rename of symlinks
  2. The old pattern. Here link1 is the old pattern which will be replaced
  3. The new pattern. Here link2 is the new pattern with which link1 will be replaced

The script recursively reads all symlinks in the directory using find "$DIR" -type l and processes it line by line.

$line is the symlink which needs to be renamed

CUR_LINK_PATH is the old path

NEW_LINK_PATH is obtained by performing string replacement in the old link path.

The old symlink is removed and new symlink is created using ln -s "$NEW_LINK_PATH" "$line"

1

I ended up writing a command-line PHP script which seems to do the trick.

<?php
//Run via command-line
$dir = new RecursiveDirectoryIterator('.');
foreach(new RecursiveIteratorIterator($dir) as $file) {
    //$link = readlink($file);
    if(is_link($file)) {
        $old_link = readlink($file);
        $new_link = str_ireplace("/joomla/", "/qa/", $old_link);
        if($new_link != $old_link) {
            exec('rm "'.$file.'"');
            exec('ln -fs "'.$new_link.'" "'.$file.'"');
            $new_link = readlink($file);
            if($new_link == $old_link) {
                echo $file."\t".$old_link."\t".$new_link."\n";
            }
        }
    }
}
?>
0

I came across this while trying to change symbolic links for PHP fpm from version 8.0 to 8.1. This command is what worked for me without a lot of hassle.

find /etc/php/8.1/fpm/conf.d -type l -lname '/etc/php/8.0/mods-available/*' -exec bash -c "readlink {} |sed s/8.0/8.1/| xargs -I % ln -nsf % {}" \;
1
  • Hi, Code only answers are not complete. For instance, its impossible to figure out if the answer is correct without looking at references. So, please edit you answer and explain what each bit does, Commented Sep 28, 2023 at 14:19
0

Quick One-liner

find /home/user/public_html/qa -type l -print0 | xargs -0 -n1 -P0 sh -c 'NEWLINK=$(readlink "$3" | sed -e "s|${1}|${2}|"); ln -snf "$NEWLINK" "$3";' _ "/dev/" "/qa/"

Shouldn't grab anything with dev or qa in their name.

Explain

find <PATH> -type l -print0

Find all links under PATH

xargs -0 -n1 -P0 <CMD>

For every input (null delimited), run CMD with 1 parameter, as parallel as possible

CMD = sh -c '<SCMD>' _ "ARG1" "ARG2" ARG3

Run a shell SCMD with arguments (3 in this case)

SCMD

NEWLINK=$(readlink "$3" | sed -e "s|${1}|${2}|"); 
ln -snf "$NEWLINK" "$3";

Get original link then modify with sed using ARG1 and ARG2.
Update link with new value.

Can be compressed to: ln -snf $(readlink "$3" | sed -e "s|${1}|${2}|") "$3" but then becomes less readable and could break with /some/path with spaces/here in certain contexts

2
  • Why all these bold character? They make the answer unreadable.
    – Toto
    Commented May 22 at 14:33
  • @Toto Can you explain? Do you men the placeholders in the eplainer?
    – WesAtWork
    Commented May 23 at 8:26

You must log in to answer this question.

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