Convierta un enlace simbólico absoluto en un enlace simbólico relativo con un simple comando de Linux

29

Tengo una completa sub-sistema de archivos dentro de un trazado /home/user/systemque contiene la estructura estándar de Linux con los directorios /bin, /home, /root, /usr, /var, /etc, ...

Este subsistema de archivos contiene enlaces simbólicos, relativos o absolutos. Los enlaces simbólicos relativos están bien, permanecen dentro del subsistema de archivos debajo /home/user/system. Pero los enlaces simbólicos absolutos son problemáticos, ya que apuntan a un objetivo fuera del subsistema de archivos.

Como ejemplo, asumimos un enlace simbólico absoluto de la siguiente manera (visto dentro del subsistema de archivos):

/usr/file1 -> /usr/lib/file1

En el sistema de archivos general, tenemos un enlace /home/user/system/usr/file1que ahora apunta a un archivo /usr/lib/file1fuera del subsistema de archivos, en lugar de un archivo /home/user/system/usr/lib/file1 dentro del subsistema de archivos.

Me gustaría tener un script simple, preferiblemente una sola línea de comando (rsync, chroot, find, ...) que convierta cada enlace simbólico absoluto en uno relativo.

En el ejemplo dado, ese enlace relativo se convertiría en

/usr/file1 -> ../usr/lib/file1
Alex
fuente
44
Para aquellos interesados ​​en tratar de resolver esta Q, aquí hay un usuario de 250,000 representantes del intento de SO: stackoverflow.com/questions/2564634/… . Hay varias soluciones potenciales en ese hilo, pero cada una tiene situaciones específicas que trata y otras que no.
slm

Respuestas:

20

Con la symlinksutilidad de Mark Lord (ofrecida por muchas distribuciones; si la suya no la tiene, compílela desde la fuente ):

chroot /home/user/system symlinks -cr .

Alternativamente, en sistemas que tienen un readlinkcomando y un -lnamepredicado para find(advertencia: código no probado):

cd /home/user/system &&
find . -lname '/*' -exec ksh -c '
  for link; do
    target=$(readlink "$link")
    link=${link#./}
    root=${link//+([!\/])/..}; root=${root#/}; root=${root%..}
    rm "$link"
    ln -s "$root${target#/}" "$link"
  done
' _ {} +
Gilles 'SO- deja de ser malvado'
fuente
¡Esta sería una buena solución! Sin embargo, ¿qué significa la expresión _ {} +al final del hallazgo? Además, find: paths must precede expression: kshaparece un error que no parece tener sentido (ya que la ruta precede a la expresión ksh).
Alex
@Alex _es $0para el fragmento de cáscara, y {} +se sustituye por la lista de argumentos que se convierten $1, $2, etc., que for link; do …los bucles sobre (que es sinónimo de for link in "$@"; do …). El error de findse debe a un error tipográfico (de alguna manera logré escribir comillas inversas en lugar de comillas simples alrededor del argumento -lname).
Gilles 'SO- deja de ser malvado'
Ahora se ejecuta el script, pero no como se esperaba. Tal vez no fui lo suficientemente preciso, he actualizado la pregunta.
Alex
@ Alex ¿Cuál es el problema? Creo que el principio es sólido, pero es posible que haya cometido algunos errores de codificación. ¿Lo intentaste symlinks? Solucionaría su problema: para eso fue básicamente para lo que fue diseñado (con la molestia de que tiene que ejecutarlo chrooteado ( fakechroot debería ser el truco)).
Gilles 'SO- deja de ser malvado'
1
Yo prefiero una solución que funcione de inmediato, sin la necesidad de instalar algo adicional.
Alex
4

Pure bash & coreutils, cambia los enlaces simbólicos a relativos sin ../s innecesarios en la ruta:

find . -type l | while read l; do
    target="$(realpath "$l")"
    ln -fs "$(realpath --relative-to="$(dirname "$(realpath -s "$l")")" "$target")" "$l"
done

Tu puedes cambiar:

  • find .para find /path/to/directoryconvertir enlaces simbólicos en ese directorio
  • ln -fsa echo ln -fsun simulacro

Explicación:

  • target="$(realpath "$l")" - encuentra la ruta absoluta al objetivo del enlace simbólico
  • ln -fs- crea symlink ( -s), forzando ( -f) reescribir de existente
  • realpath -s "$l" - encuentra la ruta absoluta al enlace simbólico
  • dirname "$(realpath -s "$l")" - encuentra la ruta absoluta al directorio que contiene el enlace simbólico
  • realpath --relative-to="$(dirname "$(realpath -s "$l")")" "$target" - encuentra la ruta del objetivo en relación con el enlace simbólico, en otras palabras: convierte la ruta absoluta en relativa
LUMIFAZA
fuente
1
Sugeriría agregar una ifcláusula para omitir los enlaces rotos.
fuenfundachtzig
0

Este fragmento de bash debería hacerlo. El primer formulario imprimirá lo que haría; el segundo lo hará. Revisaría cuidadosamente el resultado, ya que es destructivo: ln -fsobrescribe el enlace existente.

TOP=/home/user/system
cd $TOP
find * -type l -print | while read l; do echo ln -sf $TOP$(readlink $l) $l; done
find * -type l -print | while read l; do      ln -sf $TOP$(readlink $l) $l; done
qneill
fuente
Además del hecho de que esto fallará para los nombres con espacios, ¿no es este el camino equivocado? Prefija el camino absoluto?
fuenfundachtzig
0

Aquí hay una solución sh pura.

cd / inicio / usuario / sistema &&
encontrar . -nombre '/ *' |
mientras lee l; hacer
  echo ln -sf $ (echo $ (echo $ l | sed 's | / [^ /] * | / .. | g') $ (readlink $ l) | sed 's /.....//' ) $ l
hecho |
sh
Diomidis Spinellis
fuente
0

La transformación absoluta en enlaces relativos es compatible con sshfs, que monta directorios remotos a través de ssh.

El comando es: hay

sudo sshfs <remote_user>@<remote_ip_address>:/ /home/<host_user>/mntpoint/ -o transform_symlinks -o allow_other

El comando, especialmente el <placeholders>, se adaptará al entorno específico.

user260694
fuente