How to recursively edit path of symlinks?
This is an additional question that began with this one - How to edit Symbolic Links in OS X?
Now that I know how to edit the path symlinks point to, I'm trying to figure out how to do this recursively for any broken symlink. I'm a PHP guy, so this CLI stuff is somewhat foreign to me...
There seems to be a few specific elements to this, namely:
- Identify a broken symlink and retrieve its location
- Get the old, now-incorrect, destination for the symlink
- Construct the new correct path for the symlink to point to
- Edit the symlink
The first is fairly easy:
find /path/to/search -type l | while read f; do if [ ! -e "$f" ]; then ls -l "$f"; fi; done
This gets me a nice list of all broken symlinks found below the supplied directory. But I believe I would need to store the location of each in a variable (array?) to work with it.
Next, it seems that changing the action supplied in the "then" portion of the logic, would yield the solution.
Item 2 can be retrieved using:
readlink /path/to/broken/symlink
So it seems we need to shove that into a variable. I'm unclear on how to do this in terminal.
Number 3 would be a simple edit to the path retrieved during step 2. I need to replace the old drive name, with the new. So changing:
/Volumes/Old\ Drive/path/to/symlink
to
/Volumes/New\ Drive/path/to/symlink
Also unclear on exactly how to do this within a CLI script. Some sort of string replacement appears to be needed. Something like str_replace in the PHP world.
Finally step 4 can be done via:
ln -f -s /path/to/new/location/of/original /path/to/location/of/broken/symlink/
as detailed in my other question, previously linked above.
How exactly would I string these concepts together, to accomplish my desired outcome of fixing all my symlinks in one shot?
Solution 1:
From the top of my head (and without any testing at all):
-
Create a text file with the following content
#!/bin/bash find "$1" -type l | while read symlink; do if [ ! -e "$symlink" ]; then old_path=$(readlink "$symlink") new_path=${old_path/Old Drive/New Drive} ln -f -s "$new_path" "$symlink" fi done
Make it executable by running
chmod +x name-of-file
- Run
./name-of-file /path/to/search
This is untested so try it with a sample directory first.
To add some explanations
-
old_path=$(readlink "$symlink")
runs the command in$(...)
and assigns the result to$old_path
-
${old_path/Old Drive/New Drive}
does text substitution on$old_path
Solution 2:
In a bash
shell, to set a variable, simply use set NAME=bob
or set VITAL_SIGNS=none
.
You can also set a variable using the output of a command by calling the bash
-builtin function read
to assign the output to a named variable. This works well in a pipe stream like so:
ls -l | wc -l | read NUMBER_OF_LINES
Or you can assign the output directly to a variable like so:
LICENSE_KEY=$(cat ~/software/key.txt | grep KEY | awk '{print $1}')
A great way to recursively read variables is in a loop as follows:
for BROKEN_LINK in $(commands to produce a list of files)
do
commands here to sort your links out, noting that the broken links are stored in the variable $BROKEN_LINKS
done
With the above in mind, something like the following should work:
prove a folder doesn't exist
StuffeMac:dan stuffe$ ls ~/Desktop/broken_links
ls: /Users/stuffe/Desktop/broken_links: No such file or directory
prove a new target folder does exist
StuffeMac:dan stuffe$ ls ~/Desktop/working_links
StuffeMac:dan stuffe$
create some invalid and valid links
StuffeMac:dan stuffe$ ln -s ~/Desktop/brokenlinks/dan1
StuffeMac:dan stuffe$ ln -s ~/Desktop/brokenlinks/dan2
StuffeMac:dan stuffe$ ln -s ~/Desktop/working_links/dan3
StuffeMac:dan stuffe$ ln -s ~/Desktop/outofscopedeadlinks/dan4
StuffeMac:dan stuffe$ ls -l
total 32
lrwxr-xr-x 1 stuffe staff 38 8 Dec 10:06 dan1 -> /Users/stuffe/Desktop/brokenlinks/dan1
lrwxr-xr-x 1 stuffe staff 38 8 Dec 10:06 dan2 -> /Users/stuffe/Desktop/brokenlinks/dan2
lrwxr-xr-x 1 stuffe staff 40 8 Dec 10:06 dan3 -> /Users/stuffe/Desktop/working_links/dan3
lrwxr-xr-x 1 stuffe staff 46 8 Dec 10:21 dan4 -> /Users/stuffe/Desktop/outofscopedeadlinks/dan4
grab a list of dead links into a file for input
StuffeMac:dan stuffe$ find . -type l | while read f; do if [ ! -e "$f" ]; then ls "$f" >> deadlinks.txt; fi; done
StuffeMac:dan stuffe$ more deadlinks.txt
./dan1
./dan2
./dan4
run a loop against each dead link
StuffeMac:dan stuffe$ for DEAD_LINK in $(cat deadlinks.txt)
> do
> DESTINATION_IN_SCOPE=$(readlink $DEAD_LINK | grep brokenlinks | wc -l)
> NEW_DESTINATION="~/Desktop/working_links/"
> if [ $DESTINATION_IN_SCOPE = "1" ]
> then
> NEW_LINK=$(echo $DEAD_LINK | colrm 1 2)
> ln -f -s $NEW_DESTINATION$NEW_LINK $DEAD_LINK
> else
> echo "Link $DEAD_LINK not in target folder"
> fi
> done
Link ./dan4 not in target folder
StuffeMac:dan stuffe$
check out the symlinks after edits
StuffeMac:dan stuffe$ ls -l
total 32
lrwxr-xr-x 1 stuffe staff 28 8 Dec 10:08 dan1 -> ~/Desktop/working_links/dan1
lrwxr-xr-x 1 stuffe staff 28 8 Dec 10:08 dan2 -> ~/Desktop/working_links/dan2
lrwxr-xr-x 1 stuffe staff 40 8 Dec 10:06 dan3 -> /Users/stuffe/Desktop/working_links/dan3
lrwxr-xr-x 1 stuffe staff 46 8 Dec 10:21 dan4 -> /Users/stuffe/Desktop/outofscopedeadlinks/dan4