Exemple:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
Comment créer la magie (si tout va bien pas du code trop compliqué ...)?
bash
shell
path
relative-path
absolute-path
Paul Tarjan
la source
la source
realpath --relative-to=$absolute $current
.Réponses:
Utiliser realpath de GNU coreutils 8.23 est le plus simple, je pense:
Par exemple:
la source
$ realpath --relative-to="${PWD}" "$file"
est utile si vous voulez les chemins relatifs au répertoire de travail courant./usr/bin/nmap/
chemin mais pas pour/usr/bin/nmap
: denmap
à/tmp/testing
c'est seulement../../
et pas 3 fois../
. Cela fonctionne cependant, car faire..
sur les rootfs l'est/
.--relative-to=…
attend un répertoire et NE PAS vérifier. Cela signifie que vous vous retrouvez avec un "../" supplémentaire si vous demandez un chemin par rapport à un fichier (comme cet exemple semble le faire, car il/usr/bin
contient rarement ou jamais de répertoires etnmap
est normalement un binaire)donne:
la source
relpath(){ python -c "import os.path; print os.path.relpath('$1','${2:-$PWD}')" ; }
python -c 'import os, sys; print(os.path.relpath(*sys.argv[1:]))'
fonctionne de manière plus naturelle et fiable.Il s'agit d'une amélioration corrigée et entièrement fonctionnelle de la solution actuellement la mieux notée de @pini (qui ne gère malheureusement que quelques cas)
Rappel: test '-z' si la chaîne est de longueur nulle (= vide) et test '-n' si la chaîne n'est pas vide.
Cas de test:
la source
source=$1; target=$2
parsource=$(realpath $1); target=$(realpath $2)
realpath
est recommandé,source=$(readlink -f $1)
etc. si realpath n'est pas disponible (non standard)$source
et$target
comme ceci: `if [[-e $ 1]]; alors source = $ (readlink -f $ 1); else source = $ 1; fi si [[-e $ 2]]; alors target = $ (readlink -f $ 2); sinon cible = 2 $; fi` De cette façon, la fonction pourrait gérer des chemins relatifs réels / existants ainsi que des répertoires fictifs.readlink
une-m
option qui fait juste ça;)la source
Il est intégré à Perl depuis 2001, il fonctionne donc sur presque tous les systèmes que vous pouvez imaginer, même VMS .
De plus, la solution est facile à comprendre.
Donc pour votre exemple:
... fonctionnerait bien.
la source
say
n'a pas été disponible en perl pour le journal, mais il pourrait être utilisé efficacement ici.perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"
etperl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"
. Le premier script perl d' une ligne utilise un argument (l'origine est le répertoire de travail actuel). Le deuxième script perl d' une ligne utilise deux arguments.perl
peut être trouvé presque partout, bien que la réponse soit toujours à sens unique.En supposant que vous avez installé: bash, pwd, dirname, echo; alors relpath est
J'ai joué la réponse de pini et quelques autres idées
Remarque : Cela nécessite que les deux chemins d'accès soient des dossiers existants. Les fichiers ne fonctionneront pas .
la source
Python
os.path.relpath
tant que fonction shellLe but de cet
relpath
exercice est d'imiter laos.path.relpath
fonction de Python 2.7 (disponible à partir de Python version 2.6 mais ne fonctionnant correctement qu'en 2.7), comme proposé par xni . Par conséquent, certains résultats peuvent différer des fonctions fournies dans d'autres réponses.(Je n'ai pas testé avec des sauts de ligne dans les chemins simplement parce que cela rompt la validation basée sur l'appel
python -c
depuis ZSH. Ce serait certainement possible avec un certain effort.)En ce qui concerne la «magie» dans Bash, j'ai renoncé à chercher de la magie dans Bash il y a longtemps, mais j'ai depuis trouvé toute la magie dont j'ai besoin, puis certaines, dans ZSH.
Par conséquent, je propose deux implémentations.
La première implémentation vise à être entièrement compatible POSIX . Je l'ai testé avec
/bin/dash
sur Debian 6.0.6 «Squeeze». Il fonctionne également parfaitement avec/bin/sh
OS X 10.8.3, qui est en fait la version Bash 3.2 se faisant passer pour un shell POSIX.La deuxième implémentation est une fonction shell ZSH qui est robuste contre plusieurs barres obliques et autres nuisances dans les chemins. Si vous avez ZSH disponible, c'est la version recommandée, même si vous l'appelez dans le formulaire de script présenté ci-dessous (c'est-à-dire avec un shebang de
#!/usr/bin/env zsh
) à partir d'un autre shell.Enfin, j'ai écrit un script ZSH qui vérifie la sortie de la
relpath
commande trouvée dans$PATH
les cas de test fournis dans d'autres réponses. J'ai ajouté du piquant à ces tests en ajoutant des espaces, des tabulations et des signes de ponctuation comme! ? *
ici et là et j'ai également ajouté un autre test avec des caractères UTF-8 exotiques trouvés dans vim-powerline .Fonction shell POSIX
Tout d'abord, la fonction shell compatible POSIX. Il fonctionne avec une variété de chemins, mais ne nettoie pas plusieurs barres obliques et ne résout pas les liens symboliques.
Fonction shell ZSH
Maintenant, la
zsh
version la plus robuste . Si vous souhaitez qu'il résout les arguments en chemins réels à larealpath -f
(disponibles dans lecoreutils
paquet Linux ), remplacez les:a
lignes 3 et 4 par:A
.Pour l'utiliser dans zsh, supprimez la première et la dernière ligne et placez-les dans un répertoire qui se trouve dans votre
$FPATH
variable.Script de test
Enfin, le script de test. Il accepte une option, à savoir
-v
activer la sortie détaillée.la source
/
j'ai peur.Le script shell ci-dessus a été inspiré par pini (Merci!). Il déclenche un bogue dans le module de coloration syntaxique de Stack Overflow (au moins dans mon cadre d'aperçu). Veuillez donc ignorer si la mise en surbrillance est incorrecte.
Quelques notes:
À l'exception des séquences de barre oblique inverse mentionnées, la dernière ligne de la fonction "relPath" affiche des noms de chemin compatibles avec python:
La dernière ligne peut être remplacée (et simplifiée) par une ligne
Je préfère ce dernier car
Les noms de fichiers peuvent être directement ajoutés aux chemins dir obtenus par relPath, par exemple:
Les liens symboliques dans le même répertoire créé avec cette méthode n'ont pas le laid
"./"
ajouté au nom de fichier.Liste de code pour les tests de régression (ajoutez-la simplement au script shell):
la source
Pas beaucoup de réponses ici sont pratiques pour une utilisation quotidienne. Puisqu'il est très difficile de le faire correctement en pure bash, je suggère la solution fiable suivante (similaire à une suggestion enterrée dans un commentaire):
Ensuite, vous pouvez obtenir le chemin relatif en fonction du répertoire actuel:
ou vous pouvez spécifier que le chemin soit relatif à un répertoire donné:
Le seul inconvénient est que cela nécessite python, mais:
Notez que les solutions qui incluent
basename
oudirname
ne sont pas nécessairement meilleures, car elles nécessitent d'coreutils
être installées. Si quelqu'un a unebash
solution pure , fiable et simple (plutôt qu'une curiosité alambiquée), je serais surpris.la source
Ce script donne des résultats corrects uniquement pour les entrées qui sont des chemins absolus ou des chemins relatifs sans
.
ou..
:la source
J'utiliserais simplement Perl pour cette tâche pas si banale:
la source
perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"
pour que l'absolu et le courant soient cités.relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current")
. Cela garantit que les valeurs ne peuvent pas elles-mêmes contenir du code perl!Une légère amélioration sur les réponses de Kasku et Pini , qui joue mieux avec les espaces et permet de passer des chemins relatifs:
la source
test.sh:
Essai:
la source
readlink
sur$(pwd)
.Encore une autre solution, pure
bash
+ GNUreadlink
pour une utilisation facile dans le contexte suivant:Cela fonctionne dans presque tous les Linux actuels. Si
readlink -m
cela ne fonctionne pas à vos côtés, essayezreadlink -f
plutôt. Voir également https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 pour les mises à jour possibles:Remarques:
*
ou?
.ln -s
:relpath / /
donne.
et non la chaîne viderelpath a a
donnea
, même s'ila
se trouve être un répertoirereadlink
est donc requise pour canoniser les chemins.readlink -m
cela, cela fonctionne aussi pour les chemins pas encore existants.Sur les anciens systèmes, où il
readlink -m
n'est pas disponible,readlink -f
échoue si le fichier n'existe pas. Vous avez donc probablement besoin d'une solution de contournement comme celle-ci (non testée!):Ce n'est pas vraiment tout à fait correct dans le cas où
$1
inclut.
ou..
pour les chemins inexistants (comme dans/doesnotexist/./a
), mais cela devrait couvrir la plupart des cas.(Remplacer
readlink -m --
ci-dessus parreadlink_missing
.)Modifier à cause du downvote suit
Voici un test, que cette fonction, en effet, est correcte:
Perplexe? Eh bien, ce sont les bons résultats ! Même si vous pensez que cela ne correspond pas à la question, voici la preuve que c'est correct:
Sans aucun doute,
../bar
c'est le chemin relatif exact et seul correct de la pagebar
vu depuis la pagemoo
. Tout le reste serait tout simplement faux.Il est trivial d'adopter la sortie à la question qui suppose apparemment, c'est
current
un répertoire:Cela revient exactement à ce qui était demandé.
Et avant de lever un sourcil, voici une variante un peu plus complexe de
relpath
(repérer la petite différence), qui devrait également fonctionner pour la syntaxe URL (donc une traînée/
survit, grâce à quelquesbash
-magiques):Et voici les contrôles pour être clair: cela fonctionne vraiment comme indiqué.
Et voici comment cela peut être utilisé pour donner le résultat souhaité de la question:
Si vous trouvez quelque chose qui ne fonctionne pas, faites-le moi savoir dans les commentaires ci-dessous. Merci.
PS:
Pourquoi les arguments de
relpath
"renversé" contrastent-ils avec toutes les autres réponses ici?Si vous changez
à
alors vous pouvez laisser le 2ème paramètre à l'écart, de telle sorte que la BASE soit le répertoire / URL / quoi que ce soit. Ce n'est que le principe Unix, comme d'habitude.
Si vous n'aimez pas cela, veuillez revenir à Windows. Merci.
la source
Malheureusement, la réponse de Mark Rushakoff (maintenant supprimée - elle faisait référence au code d' ici ) ne semble pas fonctionner correctement lorsqu'elle est adaptée à:
La pensée décrite dans le commentaire peut être affinée pour la faire fonctionner correctement dans la plupart des cas. Je suis sur le point de supposer que le script prend un argument source (où vous êtes) et un argument cible (où vous voulez vous rendre), et que les deux sont des chemins absolus ou les deux sont relatifs. Si l'un est absolu et l'autre relatif, le plus simple est de préfixer le nom relatif avec le répertoire de travail actuel - mais le code ci-dessous ne le fait pas.
Il faut se méfier
Le code ci-dessous est sur le point de fonctionner correctement, mais n'est pas tout à fait correct.
xyz/./pqr
».xyz/../pqr
»../
' des chemins.Le code de Dennis est meilleur car il corrige 1 et 5 - mais a les mêmes problèmes 2, 3, 4. Utilisez le code de Dennis (et votez-le plus tôt) à cause de cela.
(NB: POSIX fournit un appel système
realpath()
qui résout les chemins d'accès afin qu'il n'y ait plus de liens symboliques. Appliquer cela aux noms d'entrée, puis utiliser le code de Dennis donnerait la bonne réponse à chaque fois. Il est trivial d'écrire le code C qui wrapsrealpath()
- je l'ai fait - mais je ne connais pas d'utilitaire standard qui le fasse.)Pour cela, je trouve Perl plus facile à utiliser que shell, bien que bash ait un support décent pour les tableaux et pourrait probablement le faire aussi - exercice pour le lecteur. Donc, étant donné deux noms compatibles, divisez-les chacun en composants:
Donc:
Script de test (les crochets contiennent un blanc et un onglet):
Sortie du script de test:
Ce script Perl fonctionne assez bien sur Unix (il ne prend pas en compte toutes les complexités des noms de chemin Windows) face à des entrées étranges. Il utilise le module
Cwd
et sa fonctionrealpath
pour résoudre le vrai chemin des noms qui existent et fait une analyse textuelle pour les chemins qui n'existent pas. Dans tous les cas sauf un, il produit la même sortie que le script de Dennis. Le cas déviant est:Les deux résultats sont équivalents - mais pas identiques. (La sortie provient d'une version légèrement modifiée du script de test - le script Perl ci-dessous imprime simplement la réponse, plutôt que les entrées et la réponse comme dans le script ci-dessus.) Maintenant: dois-je éliminer la réponse qui ne fonctionne pas? Peut être...
la source
/home/part1/part2
à/
a un trop grand nombre../
. Sinon, mon script correspond à votre sortie, sauf que le mien ajoute un inutile.
à la fin de celui où se trouve la destination.
et je n'utilise pas un./
au début de ceux qui descendent sans monter.readlink /usr/bin/vi
donne/etc/alternatives/vi
, mais c'est un autre lien symbolique - alors quereadlink -f /usr/bin/vi
donne/usr/bin/vim.basic
, qui est la destination ultime de tous les liens symboliques ...J'ai pris votre question comme un défi pour l'écrire en code shell "portable", c'est-à-dire
Il s'exécute sur tout shell conforme POSIX (zsh, bash, ksh, ash, busybox, ...). Il contient même une suite de tests pour vérifier son fonctionnement. La canonisation des chemins d'accès est laissée en exercice. :-)
la source
Ma solution:
la source
Voici ma version. Il est basé sur la réponse de @Offirmo . Je l'ai rendu compatible avec Dash et corrigé l'échec du testcase suivant:
./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../..f/g/"
Maintenant:
CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../../../def/g/"
Voir le code:
la source
Je suppose que celui-ci fera aussi l'affaire ... (livré avec des tests intégrés) :)
OK, des frais généraux sont attendus, mais nous faisons du Bourne shell ici! ;)
la source
Ce script ne fonctionne que sur les noms de chemin. Il ne nécessite aucun fichier pour exister. Si les chemins passés ne sont pas absolus, le comportement est un peu inhabituel, mais il devrait fonctionner comme prévu si les deux chemins sont relatifs.
Je ne l'ai testé que sur OS X, donc ce n'est peut-être pas portable.
la source
Cette réponse ne répond pas à la partie Bash de la question, mais parce que j'ai essayé d'utiliser les réponses de cette question pour implémenter cette fonctionnalité dans Emacs je vais la jeter ici.
Emacs a en fait une fonction prête à l'emploi:
la source
relpath
fonction) se comporte de la même manièrefile-relative-name
pour les cas de test que vous avez fournis.Voici un script shell qui le fait sans appeler d'autres programmes:
source: http://www.ynform.org/w/Pub/Relpath
la source
J'avais besoin de quelque chose comme ça mais qui résolvait aussi les liens symboliques. J'ai découvert que pwd a un drapeau -P à cet effet. Un fragment de mon script est joint. C'est dans une fonction dans un script shell, d'où les $ 1 et $ 2. La valeur de résultat, qui est le chemin relatif de START_ABS à END_ABS, se trouve dans la variable UPDIRS. Les cd de script se trouvent dans chaque répertoire de paramètres afin d'exécuter le pwd -P et cela signifie également que les paramètres de chemin relatifs sont traités. À la vôtre, Jim
la source