How change symlink path for many files?
Asked Answered
E

1

5

I was changed directory name. In this directory thousands of files. Some projects use this files, projects have got symlinks on it.

  1. How to find all symlinks, which have got folder name in their address?
  2. how to change all this symlinks to another path in automatic mode?

if 2 only bash scripting with deleting and creating new - i will do it, but may be you know more easy way?

Emmanuelemmeline answered 24/6, 2015 at 7:21 Comment(0)
A
18
  1. 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;' \;
    
  2. 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.

Breaking it down

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 string
        • readlink ... - find out where the symlink points to
        • p="$(...)" - 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 directory
        • echo "$(pwd)/$p" | - pipe the above to the next command
        • sed ... - resolve all ..'s, see here
        • p="$(...)" 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 string
      • then - 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 same
      • fi; - end if
  • \; - delimiter for -execdir
Addictive answered 24/6, 2015 at 10:25 Comment(3)
Was the breakdown section automatically generated with a tool or did you type all that out manually?Methanol
@Methanol Manually.Addictive
Thanks for this. It would have taken a good hour for me to figure out how to script something that esoteric, and now it's a shell script that I've used twice today so far during a painful server migration (because Apple "helpfully" decided to not allow users to put symlinks in / anymore). Much appreciated.Sommersommers

© 2022 - 2024 — McMap. All rights reserved.