Batch Update Symbolic Links Recursively

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


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

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.


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"