I was changed directory name. In this directory thousands of files. Some projects use this files, projects have got symlinks on it.
if 2 only bash scripting with deleting and creating new - i will do it, but may be you know more easy way?
It's a bit complicated, but it can be done with find
, readlink
, a check to test whether the symlink is relative or not, and sed
to get rid of ..
in path names (copied 1:1 from this answer).
(Note that most convenient methods (such as readlink -f
) are not available due to the symlinks targets not existing anymore.)
Assuming your old path is /var/lib/old/path
:
oldpath='/var/lib/old/path';
find / -type l -execdir bash -c 'p="$(readlink "{}")"; if [ "${p:0:1}" != "/" ]; then p="$(echo "$(pwd)/$p" | sed -e "s|/\./|/|g" -e ":a" -e "s|/[^/]*/\.\./|/|" -e "t a")"; fi; if [ "${p:0:'${#oldpath}'}" == "'"$oldpath"'" ]; then ...; fi;' \;
Now replace the ...
from above with ln -sf
(-f
to override the existing link).
Assuming your new path is /usr/local/my/awesome/new/path
:
oldpath='/var/lib/old/path';
newpath='/usr/local/my/awesome/new/path';
find / -type l -execdir bash -c 'p="$(readlink "{}")"; if [ "${p:0:1}" != "/" ]; then p="$(echo "$(pwd)/$p" | sed -e "s|/\./|/|g" -e ":a" -e "s|/[^/]*/\.\./|/|" -e "t a")"; fi; if [ "${p:0:'${#oldpath}'}" == "'"$oldpath"'" ]; then ln -sf "'"$newpath"'${p:'${#oldpath}'}" "{}"; fi;' \;
Note that oldpath
and newpath
have to be absolute paths.
Also note that this will convert all relative symlinks to absolute ones.
It would be possible to keep them relative, but only with a lot of effort.
For those of you who care what that one-line-inferno actually means:
find
- a cool executable/
- where to search, in this case the system root-type l
- match symbolic links-execdir
- for every match run the following command in the directory of the matched file:
bash
- well, bash-c
- execute the following string (leading and trailing '
removed):
p="$(readlink "{}")";
- starting with the most inner:
"
- start a string to make sure no expansion happens{}
- placeholder for the matched file's name (feature of -execdir
)"
- end the stringreadlink ...
- find out where the symlink points top="$(...)"
- and store the result in $p
if [ "${p:0:1}" != "/" ]; then
- if the first character of $p
is /
(i.e. the symlink is absolute), then...p="$(echo "$(pwd)/$p" | sed -e "s|/\./|/|g" -e ":a" -e "s|/[^/]*/\.\./|/|" -e "t a")";
- convert the path to an absolute one:
$(pwd)
- the current directory (where the matched file lies, because we're using -execdir
)/$p
- append a slash and the target of the symlink to the path of the working directoryecho "$(pwd)/$p" |
- pipe the above to the next commandsed ...
- resolve all ..
's, see herep="$(...)"
and store the result back into $p
.fi;
- end if
if [ "${p:0:'${#oldpath}'}" == "'"$oldpath"'" ];
- if $p
starts with $oldpath
${p:0:'${#oldpath}'}
- substring of $p
, starting at position 0
, with length of $oldpath
:
${#oldpath}
- length of variable $oldpath
'...'
- required because we're inside a '
-quoted stringthen
- then...ln -sf
- link symbolically and override existing file, with arguments:
"'"$newpath"'${p:'${#oldpath}'}"
- replace the $oldpath
part of $p
with $newpath
(actually remove as many characters from $p
as $oldpath
long is, and prepend $newpath
to it):
"
- start a string'
- end the '
-string argument to bash -c
"
- append a "
-string to it (in which variable expansion happens), containing:$newpath
- the value of $newpath
"
- end the "
-string argument to bash -c
'
- append a '
-string to it, containing:${p:
- a substring of p
, starting at:'
- end the argument to bash -c
${#oldpath}
- append the length of $oldpath
to it'
- append another '
-string to it}
- end substring"
- end string"{}"
- the link file, whose path stays the samefi;
- end if
\;
- delimiter for -execdir