Obtention de liens relatifs entre deux chemins

21

Disons que j'ai deux chemins: <source_path>et <target_path>. J'aimerais que mon shell (zsh) découvre automatiquement s'il existe un moyen de représenter à <target_path>partir <source_path>d'un chemin relatif.

Par exemple, supposons

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

Le résultat serait ../../hello/world

Pourquoi j'en ai besoin:

J'ai besoin de créer un lien symbolique de <source_path>à <target_path>utiliser un lien symbolique relatif dans la mesure du possible, car sinon notre serveur samba n'affiche pas le fichier correctement lorsque j'accède à ces fichiers sur le réseau à partir de Windows (je ne suis pas l'administrateur sys et ne le fais pas) t avoir le contrôle sur ce paramètre)

En supposant que <target_path>et <source_path>sont des chemins absolus, ce qui suit crée un lien symbolique pointant vers un chemin absolu .

ln -s <target_path> <source_path>

donc ça ne marche pas pour mes besoins. Je dois le faire pour des centaines de fichiers, donc je ne peux pas simplement le réparer manuellement.

Y a-t-il des éléments intégrés au shell qui s'en occupent?

Amelio Vazquez-Reina
la source
Vous pouvez utiliser symlinkspour convertir des liens absolus en liens relatifs.
Stéphane Chazelas
@StephaneChazelas comment? Pourriez-vous poster une réponse expliquant?
terdon

Réponses:

10

Vous pouvez utiliser la symlinkscommande pour convertir les chemins absolus en chemins relatifs:

/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/

Nous avons des liens absolus, convertissons-les en relatifs:

/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/

Les références

Stéphane Chazelas
la source
Merci Stéphane. Cela résout le problème racine, j'ai donc accepté. Cela dit, ce serait génial d'avoir quelque chose (par exemple une fonction zsh) qui prend deux chemins (source et cible) et génère le chemin relatif de source à cible (pas de liens symboliques ici). Cela résoudrait également la question posée dans le titre.
Amelio Vazquez-Reina
@ user815423426, terdon a répondu à cette partie
Stéphane Chazelas
24

Essayez d'utiliser la realpathcommande (partie de GNU coreutils; > = 8.23 ), par exemple:

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

Si vous utilisez macOS, installez la version GNU via: brew install coreutilset utilisez grealpath.

Notez que les deux chemins doivent exister pour que la commande réussisse. Si vous avez quand même besoin du chemin relatif même si l'un d'eux n'existe pas, ajoutez le commutateur -m.

Pour plus d'exemples, voir Convertir le chemin absolu en chemin relatif en fonction d'un répertoire courant .

kenorb
la source
Je reçois realpath: unrecognized option '--relative-to=.':( version realpath 1.19
phuclv
@ LưuVĩnhPhúc Vous devez utiliser la version GNU / Linux de realpath. Si vous êtes sur Mac OS, installation via: brew install coreutils.
kenorb
non, je suis sur Ubuntu 14.04 LTS
phuclv
@ LưuVĩnhPhúc Je suis realpath (GNU coreutils) 8.26là où le paramètre est présent. Voir: gnu.org/software/coreutils/realpath Vérifiez que vous n'utilisez pas d'alias (par exemple, exécuter en tant que \realpath) et comment vous pouvez installer la version à partir de coreutils.
kenorb
7

Je pense que cette solution python (tirée du même post que la réponse de @ EvanTeitelman) mérite également d'être mentionnée. Ajoutez ceci à votre ~/.zshrc:

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

Vous pouvez alors faire, par exemple:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
terdon
la source
@StephaneChazelas, c'était une copie directe de la réponse liée. Je suis un gars de Perl et je ne connais pratiquement pas de python donc je ne sais pas comment l'utiliser sys.argv. Si le code affiché est incorrect ou peut être amélioré, n'hésitez pas à le faire avec mes remerciements.
terdon
@StephaneChazelas Je comprends maintenant, merci pour la modification.
terdon
7

Il n'y a aucun shell intégré pour s'en occuper. J'ai trouvé une solution sur Stack Overflow . J'ai copié la solution ici avec de légères modifications et marqué cette réponse comme wiki communautaire.

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"
}

Vous pouvez utiliser cette fonction comme ceci:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"
utilisateur26112
la source
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/}"
}

C'est la solution la plus simple et la plus belle au problème.

Il devrait également fonctionner sur toutes les coquilles de type bourne.

Et la sagesse est la suivante: il n'y a absolument aucun besoin d'utilitaires externes ou même de conditions compliquées. Quelques substitutions de préfixe / suffixe et une boucle while sont tout ce dont vous avez besoin pour faire à peu près tout ce qui est fait sur des coquilles de type bourne.

Également disponible via PasteBin: http://pastebin.com/CtTfvime

Arto Pekkanen
la source
Merci pour votre solution. J'ai trouvé que pour que cela fonctionne dans un, Makefilej'avais besoin d'ajouter une paire de guillemets doubles à la ligne pos=${pos%/*}(et, bien sûr, des points-virgules et des barres obliques inverses à la fin de chaque ligne).
Circonflexe
L'idée n'est pas mauvaise mais, dans la version actuelle, de nombreux cas ne fonctionnent pas: relpath /tmp /tmp , relpath /tmp /tmp/a, relpath /tmp/a /. De plus, vous oubliez de mentionner que tous les chemins doivent être absolus ET canonisés (c'est à dire /tmp/a/..ne fonctionne pas)
Jérôme Pouiller
0

J'ai dû écrire un script bash pour le faire de manière fiable (et bien sûr sans vérifier l'existence du fichier).

Usage

relpath <symlink path> <source path>

Renvoie un chemin source relatif.

exemple:

#/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

les deux obtiennent ../../CC/DD/EE


Pour avoir un test rapide, vous pouvez exécuter

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

résultat: une liste de test

/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

BTW, si vous souhaitez utiliser ce script sans l'enregistrer et que vous faites confiance à ce script, vous avez deux façons de le faire avec bash trick.

Importez en relpathtant que fonction bash en suivant la commande. (Nécessite bash 4+)

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

ou appelez le script à la volée comme avec le combo basl curl.

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
la source