10

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/?

14

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
  • 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 Jun 29 '10 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 Jun 29 '10 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 Jun 30 '10 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 Jun 30 '10 at 3:41
  • Huh, weird. Well, if you ever figure out what was going wrong, put a comment here. I'm curious. – David Z Jun 30 '10 at 3:54
5

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.

  • 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. – Kyle Strand Aug 10 '17 at 17:51
0

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 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"

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.