Obteniendo enlaces relativos entre dos caminos

21

Digamos que tengo dos caminos: <source_path>y <target_path>. Me gustaría que mi concha (zsh) para encontrar automáticamente si hay una manera de representar <target_path>de <source_path>una ruta relativa.

Por ejemplo, supongamos

  • <source_path> es /foo/bar/something
  • <target_path> es /foo/hello/world

El resultado sería ../../hello/world

Por qué necesito esto:

Yo necesito como para crear un enlace simbólico desde <source_path>que <target_path>el uso de un pariente enlace simbólico siempre que sea posible, ya que de lo contrario nuestro servidor Samba no muestra el archivo correctamente cuando accedo a estos archivos en la red desde Windows (no soy el administrador del sistema, y don' tener control sobre esta configuración)

Suponiendo que <target_path>y <source_path>son rutas absolutas, lo siguiente crea un enlace simbólico que apunta a una ruta absoluta .

ln -s <target_path> <source_path>

entonces no funciona para mis necesidades. Necesito hacer esto para cientos de archivos, por lo que no puedo solucionarlo manualmente.

¿Algún shell incorporado que se encargue de esto?

Amelio Vazquez-Reina
fuente
Puede usar symlinkspara convertir enlaces absolutos a relativos.
Stéphane Chazelas
@StephaneChazelas ¿cómo? ¿Podría publicar una respuesta explicando?
terdon

Respuestas:

10

Puede usar el symlinkscomando para convertir rutas absolutas a relativas:

/tmp$ mkdir -p 1/{a,b,c} 2
/tmp$ cd 2
/tmp/2$ ln -s /tmp/1/* .
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 a -> /tmp/1/a/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 b -> /tmp/1/b/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 c -> /tmp/1/c/

Tenemos enlaces absolutos, convirtámoslos a relativos:

/tmp/2$ symlinks -cr .
absolute: /tmp/2/a -> /tmp/1/a
changed:  /tmp/2/a -> ../1/a
absolute: /tmp/2/b -> /tmp/1/b
changed:  /tmp/2/b -> ../1/b
absolute: /tmp/2/c -> /tmp/1/c
changed:  /tmp/2/c -> ../1/c
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 a -> ../1/a/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 b -> ../1/b/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 c -> ../1/c/

Referencias

Stéphane Chazelas
fuente
Gracias Stephane Esto resuelve el problema raíz, por lo que he aceptado. Dicho esto, sería genial tener algo (por ejemplo, una función zsh) que tome dos rutas (fuente y destino) y genere la ruta relativa de fuente a destino (no hay enlaces simbólicos aquí). Eso resolvería la pregunta formulada en el título también.
Amelio Vazquez-Reina
@ user815423426, terdon ha respondido esa parte
Stéphane Chazelas
24

Intente usar el realpathcomando (parte de GNU coreutils; > = 8.23 ), por ejemplo:

realpath --relative-to=/foo/bar/something /foo/hello/world

Si está utilizando macOS, instale la versión de GNU a través de: brew install coreutilsy úsela grealpath.

Tenga en cuenta que ambas rutas deben existir para que el comando sea exitoso. Si necesita la ruta relativa de todos modos, incluso si uno de ellos no existe, agregue el modificador -m.

Para obtener más ejemplos, consulte Convertir ruta absoluta en ruta relativa dado un directorio actual .

kenorb
fuente
Obtengo realpath: unrecognized option '--relative-to=.':( realpath versión 1.19
phuclv
@ LưuVĩnhPhúc Necesita usar la versión GNU / Linux de realpath. Si estás en MacOS, instalar a través de: brew install coreutils.
kenorb
no, estoy en Ubuntu 14.04 LTS
phuclv
@ LưuVĩnhPhúc Estoy realpath (GNU coreutils) 8.26donde el parámetro está presente. Consulte: gnu.org/software/coreutils/realpath. Verifique que no esté utilizando un alias (por ejemplo, ejecutar como \realpath) y cómo puede instalar la versión desde coreutils.
kenorb
7

Creo que también vale la pena mencionar esta solución de Python (tomada de la misma publicación que la respuesta de @ EvanTeitelman). Agregue esto a su ~/.zshrc:

relpath() python -c 'import os.path, sys;\
  print os.path.relpath(sys.argv[1],sys.argv[2])' "$1" "${2-$PWD}"

Luego puede hacer, por ejemplo:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
terdon
fuente
@StephaneChazelas esta fue una copia directa de la respuesta vinculada. Soy un chico Perl y no conozco esencialmente python, así que no sé cómo usarlo sys.argv. Si el código publicado es incorrecto o puede mejorarse, no dude en hacerlo con mi agradecimiento.
terdon
@ StephaneChazelas Ahora entiendo, gracias por la edición.
terdon
7

No hay ningún cascarón incorporado para encargarse de esto. Sin embargo, encontré una solución en Stack Overflow . Copié la solución aquí con ligeras modificaciones y marqué esta respuesta como wiki de la comunidad.

relpath() {
    # both $1 and $2 are absolute paths beginning with /
    # $1 must be a canonical path; that is none of its directory
    # components may be ".", ".." or a symbolic link
    #
    # returns relative path to $2/$target from $1/$source
    source=$1
    target=$2

    common_part=$source
    result=

    while [ "${target#"$common_part"}" = "$target" ]; do
        # no match, means that candidate common part is not correct
        # go up one level (reduce common part)
        common_part=$(dirname "$common_part")
        # and record that we went back, with correct / handling
        if [ -z "$result" ]; then
            result=..
        else
            result=../$result
        fi
    done

    if [ "$common_part" = / ]; then
        # special case for root (no common path)
        result=$result/
    fi

    # since we now have identified the common part,
    # compute the non-common part
    forward_part=${target#"$common_part"}

    # and now stick all parts together
    if [ -n "$result" ] && [ -n "$forward_part" ]; then
        result=$result$forward_part
    elif [ -n "$forward_part" ]; then
        # extra slash removal
        result=${forward_part#?}
    fi

    printf '%s\n' "$result"
}

Puede usar esta función así:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"
usuario26112
fuente
3
# Prints out the relative path between to absolute paths. Trivial.
#
# Parameters:
# $1 = first path
# $2 = second path
#
# Output: the relative path between 1st and 2nd paths
relpath() {
    local pos="${1%%/}" ref="${2%%/}" down=''

    while :; do
        test "$pos" = '/' && break
        case "$ref" in $pos/*) break;; esac
        down="../$down"
        pos=${pos%/*}
    done

    echo "$down${ref##$pos/}"
}

Esta es la solución más simple y hermosa para el problema.

También debería funcionar en todos los depósitos tipo bourne.

Y la sabiduría aquí es: no hay absolutamente ninguna necesidad de utilidades externas o incluso condiciones complicadas. Un par de sustituciones de prefijos / sufijos y un bucle while es todo lo que necesita para hacer casi cualquier cosa en conchas tipo bourne.

También disponible a través de PasteBin: http://pastebin.com/CtTfvime

Arto Pekkanen
fuente
Gracias por tu solución. Descubrí que para que funcionara Makefile, necesitaba agregar un par de comillas dobles a la línea pos=${pos%/*}(y, por supuesto, punto y coma y barras diagonales al final de cada línea).
Circonflexe
Idea no es mala, pero, en la versión actual de un montón de casos no funcionan: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Además, olvida mencionar que todos los caminos deben ser absolutos Y canonicalizados (es decir /tmp/a/.., no funciona)
Jérôme Pouiller
0

Tuve que escribir un script bash para hacer esto de manera confiable (y, por supuesto, sin verificar la existencia del archivo).

Uso

relpath <symlink path> <source path>

Devuelve una ruta de origen relativa.

ejemplo:

#/a/b/c/d/e   ->  /a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath /a/b/c/d/e   /a/b/CC/DD/EE

#./a/b/c/d/e   ->  ./a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath ./a/b/c/d/e  ./a/b/CC/DD/EE

ambos consiguen ../../CC/DD/EE


Para tener una prueba rápida puedes correr

curl -sL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- --test

resultado: una lista de prueba

/a             ->  /                 ->  .
/a/b           ->  /                 ->  ..
/a/b           ->  /a                ->  .
/a/b/c         ->  /a                ->  ..
/a/b/c         ->  /a/b              ->  .
/a/b/c         ->  /a/b/CC           ->  CC
/a/b/c/d/e     ->  /a                ->  ../../..
/a/b/c/d/e     ->  /a/b              ->  ../..
/a/b/c/d/e     ->  /a/b/CC           ->  ../../CC
/a/b/c/d/e     ->  /a/b/CC/DD/EE     ->  ../../CC/DD/EE
./a            ->  ./                ->  .
./a/b          ->  ./                ->  ..
./a/b          ->  ./a               ->  .
./a/b/c        ->  ./a               ->  ..
./a/b/c        ->  ./a/b             ->  .
./a/b/c        ->  ./a/b/CC          ->  CC
./a/b/c/d/e    ->  ./a               ->  ../../..
./a/b/c/d/e    ->  ./a/b/CC          ->  ../../CC
./a/b/c/d/e    ->  ./a/b/CC/DD/EE    ->  ../../CC/DD/EE
/a             ->  /x                ->  x
/a/b           ->  /x                ->  ../x
/a/b/c         ->  /x                ->  ../../x
/a             ->  /x/y              ->  x/y
/a/b           ->  /x/y              ->  ../x/y
/a/b/c         ->  /x/y              ->  ../../x/y
/x             ->  /a                ->  a
/x             ->  /a/b              ->  a/b
/x             ->  /a/b/c            ->  a/b/c
/x/y           ->  /a                ->  ../a
/x/y           ->  /a/b              ->  ../a/b
/x/y           ->  /a/b/c            ->  ../a/b/c
./a a/b/c/d/e  ->  ./a a/b/CC/DD/EE  ->  ../../CC/DD/EE
/x x/y y       ->  /a a/b b/c c      ->  ../a a/b b/c c

Por cierto, si desea utilizar este script sin guardarlo y confía en este script, entonces tiene dos formas de hacerlo con bash trick.

Importar relpathcomo una función bash siguiendo el comando. (Requiere bash 4+)

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)

o llame al script sobre la marcha como con combo curl bash.

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
osexp2003
fuente