Convertissez le chemin absolu en chemin relatif à partir D'un répertoire courant en utilisant Bash

exemple:

absolute="/foo/bar"
current="/foo/baz/foo"

# Magic

relative="../../bar"

comment créer le code magique (avec un peu de chance, pas trop compliqué...)?

182
demandé sur Peter Mortensen 2010-04-02 05:42:38

23 réponses

$ python -c "import os.path; print os.path.relpath('/foo/bar', '/foo/baz/foo')"

donne:

../../bar
143
répondu xni 2014-07-25 19:12:37

utiliser realpath de GNU coreutils 8.23 est le plus simple, je pense:

$ realpath --relative-to="$file1" "$file2"

par exemple:

$ realpath --relative-to=/usr/bin/nmap /tmp/testing
../../../tmp/testing
126
répondu modulus0 2017-07-09 21:35:11

il s'agit d'une amélioration corrigée, entièrement fonctionnelle de la solution actuellement mieux notée de @pini (qui malheureusement ne traitent que quelques cas)

rappel: le test ' - z 'si la chaîne est de longueur zéro (=vide) et le test'-n ' si la chaîne est et non vide.

# both  and  are absolute paths beginning with /
# returns relative path to /$target from /$source
source=
target=

common_part=$source # for now
result="" # for now

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:1}"
fi

echo $result

cas D'essai:

compute_relative.sh "/A/B/C" "/A"           -->  "../.."
compute_relative.sh "/A/B/C" "/A/B"         -->  ".."
compute_relative.sh "/A/B/C" "/A/B/C"       -->  ""
compute_relative.sh "/A/B/C" "/A/B/C/D"     -->  "D"
compute_relative.sh "/A/B/C" "/A/B/C/D/E"   -->  "D/E"
compute_relative.sh "/A/B/C" "/A/B/D"       -->  "../D"
compute_relative.sh "/A/B/C" "/A/B/D/E"     -->  "../D/E"
compute_relative.sh "/A/B/C" "/A/D"         -->  "../../D"
compute_relative.sh "/A/B/C" "/A/D/E"       -->  "../../D/E"
compute_relative.sh "/A/B/C" "/D/E/F"       -->  "../../../D/E/F"
30
répondu Offirmo 2015-05-08 09:22:12
#!/bin/bash
# both  and  are absolute paths
# returns  relative to 

source=
target=

common_part=$source
back=
while [ "${target#$common_part}" = "${target}" ]; do
  common_part=$(dirname $common_part)
  back="../${back}"
done

echo ${back}${target#$common_part/}
24
répondu pini 2011-02-17 10:40:53

il est intégré à Perl depuis 2001, donc il fonctionne sur presque tous les systèmes que vous pouvez imaginer, même VMS .

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' FILE BASE

Aussi, la solution est facile à comprendre.

donc pour votre exemple:

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $absolute $current

...marcherait bien.

18
répondu Erik Aronesty 2015-09-27 21:14:52

Python os.path.relpath comme une fonction shell

le but de cet exercice relpath est d'imiter la fonction os.path.relpath de Python 2.7 (disponible à partir de la version 2.6 de Python mais ne fonctionne correctement qu'en 2.7), comme proposé par xni . En conséquence, certains résultats peuvent différer de fonctions prévues dans d'autres réponses.

(Je n'ai pas testé avec newlines dans paths simplement parce qu'il casse la validation basé sur l'appel python -c de ZSH. Ce serait certainement possible avec un certain effort.)

en ce qui concerne la" magie " à Bash, j'ai abandonné la recherche de la magie à Bash il y a longtemps, mais j'ai depuis trouvé toute la magie dont j'ai besoin, et puis certains, en ZSH.

par conséquent, je propose deux implémentations.

la première mise en œuvre vise à être entièrement conforme POSIX . Je l'ai testé avec /bin/dash Debian 6.0.6 "Squeeze". Il fonctionne également parfaitement avec /bin/sh sur OS X 10.8.3, qui est en fait Bash version 3.2 prétendant être un shell POSIX.

la seconde implémentation est une fonction shell ZSH robuste contre les coupures multiples 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 script présenté ci-dessous (i.e. 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 commande relpath trouvée dans $PATH étant donné les cas de test fournis dans d'autres réponses. J'ai ajouté un peu de piquant à ces tests en ajoutant des espaces, des onglets et des signes de ponctuation comme ! ? * ici et là et j'ai aussi ajouté un autre test avec des caractères exotiques UTF-8 trouvés dans vim-powerline .

POSIX fonction shell

tout d'abord, la fonction shell conforme à POSIX. Il fonctionne avec une variété de chemins, mais ne nettoie pas plusieurs slashes ou résoudre les liens symboliques.

#!/bin/sh
relpath () {
    [ $# -ge 1 ] && [ $# -le 2 ] || return 1
    current="${2:+""}"
    target="${2:-""}"
    [ "$target" != . ] || target=/
    target="/${target##/}"
    [ "$current" != . ] || current=/
    current="${current:="/"}"
    current="/${current##/}"
    appendix="${target##/}"
    relative=''
    while appendix="${target#"$current"/}"
        [ "$current" != '/' ] && [ "$appendix" = "$target" ]; do
        if [ "$current" = "$appendix" ]; then
            relative="${relative:-.}"
            echo "${relative#/}"
            return 0
        fi
        current="${current%/*}"
        relative="$relative${relative:+/}.."
    done
    relative="$relative${relative:+${appendix:+/}}${appendix#/}"
    echo "$relative"
}
relpath "$@"

ZSH fonction shell

maintenant, la version plus robuste zsh . Si vous souhaitez résoudre les arguments de Real paths à la realpath -f (disponible dans le paquet Linux coreutils ), remplacez le :a sur les lignes 3 et 4 par :A .

pour utiliser ceci en zsh, supprimez la première et la dernière ligne et mettez-la dans un répertoire qui est dans votre variable $FPATH .

#!/usr/bin/env zsh
relpath () {
    [[ $# -ge 1 ]] && [[ $# -le 2 ]] || return 1
    local target=${${2:-}:a} # replace `:a' by `:A` to resolve symlinks
    local current=${${${2:+}:-$PWD}:a} # replace `:a' by `:A` to resolve symlinks
    local appendix=${target#/}
    local relative=''
    while appendix=${target#$current/}
        [[ $current != '/' ]] && [[ $appendix = $target ]]; do
        if [[ $current = $appendix ]]; then
            relative=${relative:-.}
            print ${relative#/}
            return 0
        fi
        current=${current%/*}
        relative="$relative${relative:+/}.."
    done
    relative+=${relative:+${appendix:+/}}${appendix#/}
    print $relative
}
relpath "$@"

script de Test

enfin, le script de test. Il accepte une option, à savoir -v pour activer la sortie verbeuse.

#!/usr/bin/env zsh
set -eu
VERBOSE=false
script_name=$(basename "151920920")

usage () {
    print "\n    Usage: $script_name SRC_PATH DESTINATION_PATH\n" >&2
    exit ${1:=1}
}
vrb () { $VERBOSE && print -P ${(%)@} || return 0; }

relpath_check () {
    [[ $# -ge 1 ]] && [[ $# -le 2 ]] || return 1
    target=${${2:-}}
    prefix=${${${2:+}:-$PWD}}
    result=$(relpath $prefix $target)
    # Compare with python's os.path.relpath function
    py_result=$(python -c "import os.path; print os.path.relpath('$target', '$prefix')")
    col='%F{green}'
    if [[ $result != $py_result ]] && col='%F{red}' || $VERBOSE; then
        print -P "${col}Source: '$prefix'\nDestination: '$target'%f"
        print -P "${col}relpath: ${(qq)result}%f"
        print -P "${col}python:  ${(qq)py_result}%f\n"
    fi
}

run_checks () {
    print "Running checks..."

    relpath_check '/    a   b/å/⮀*/!' '/    a   b/å/⮀/xäå/?'

    relpath_check '/'  '/A'
    relpath_check '/A'  '/'
    relpath_check '/  & /  !/*/\/E' '/'
    relpath_check '/' '/  & /  !/*/\/E'
    relpath_check '/  & /  !/*/\/E' '/  & /  !/?/\/E/F'
    relpath_check '/X/Y' '/  & /  !/C/\/E/F'
    relpath_check '/  & /  !/C' '/A'
    relpath_check '/A /  !/C' '/A /B'
    relpath_check '/Â/  !/C' '/Â/  !/C'
    relpath_check '/  & /B / C' '/  & /B / C/D'
    relpath_check '/  & /  !/C' '/  & /  !/C/\/Ê'
    relpath_check '/Å/  !/C' '/Å/  !/D'
    relpath_check '/.A /*B/C' '/.A /*B/\/E'
    relpath_check '/  & /  !/C' '/  & /D'
    relpath_check '/  & /  !/C' '/  & /\/E'
    relpath_check '/  & /  !/C' '/\/E/F'

    relpath_check /home/part1/part2 /home/part1/part3
    relpath_check /home/part1/part2 /home/part4/part5
    relpath_check /home/part1/part2 /work/part6/part7
    relpath_check /home/part1       /work/part1/part2/part3/part4
    relpath_check /home             /work/part2/part3
    relpath_check /                 /work/part2/part3/part4
    relpath_check /home/part1/part2 /home/part1/part2/part3/part4
    relpath_check /home/part1/part2 /home/part1/part2/part3
    relpath_check /home/part1/part2 /home/part1/part2
    relpath_check /home/part1/part2 /home/part1
    relpath_check /home/part1/part2 /home
    relpath_check /home/part1/part2 /
    relpath_check /home/part1/part2 /work
    relpath_check /home/part1/part2 /work/part1
    relpath_check /home/part1/part2 /work/part1/part2
    relpath_check /home/part1/part2 /work/part1/part2/part3
    relpath_check /home/part1/part2 /work/part1/part2/part3/part4 
    relpath_check home/part1/part2 home/part1/part3
    relpath_check home/part1/part2 home/part4/part5
    relpath_check home/part1/part2 work/part6/part7
    relpath_check home/part1       work/part1/part2/part3/part4
    relpath_check home             work/part2/part3
    relpath_check .                work/part2/part3
    relpath_check home/part1/part2 home/part1/part2/part3/part4
    relpath_check home/part1/part2 home/part1/part2/part3
    relpath_check home/part1/part2 home/part1/part2
    relpath_check home/part1/part2 home/part1
    relpath_check home/part1/part2 home
    relpath_check home/part1/part2 .
    relpath_check home/part1/part2 work
    relpath_check home/part1/part2 work/part1
    relpath_check home/part1/part2 work/part1/part2
    relpath_check home/part1/part2 work/part1/part2/part3
    relpath_check home/part1/part2 work/part1/part2/part3/part4

    print "Done with checks."
}
if [[ $# -gt 0 ]] && [[  = "-v" ]]; then
    VERBOSE=true
    shift
fi
if [[ $# -eq 0 ]]; then
    run_checks
else
    VERBOSE=true
    relpath_check "$@"
fi
15
répondu simonair 2017-05-23 11:47:28
#!/bin/sh

# Return relative path from canonical absolute dir path  to canonical
# absolute dir path  ( and/or  may end with one or no "/").
# Does only need POSIX shell builtins (no external command)
relPath () {
    local common path up
    common=${1%/} path=${2%/}/
    while test "${path#"$common"/}" = "$path"; do
        common=${common%/*} up=../$up
    done
    path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"
}

# Return relative path from dir  to dir  (Does not impose any
# restrictions on  and  but requires GNU Core Utility "readlink"
# HINT: busybox's "readlink" does not support option '-m', only '-f'
#       which requires that all but the last path component must exist)
relpath () { relPath "$(readlink -m "")" "$(readlink -m "")"; }

ci-dessus shell script a été inspiré par pini (Merci!). Il déclenche un bug dans le module de mise en surbrillance syntaxique de Stack Overflow (au moins dans mon preview cadre.) Veuillez donc ignorer si la mise en évidence est incorrecte.

quelques notes:

  • suppression des erreurs et amélioration du code sans augmentation significative du code longueur et complexité
  • mettre la fonctionnalité dans les fonctions pour facilité d'utilisation
  • maintenaient les fonctions POSIX compatibles de sorte qu'elles (devraient) fonctionner avec toutes les fonctions POSIX shells (testé avec dash, bash, et zsh dans Ubuntu Linux 12.04)
  • a utilisé des variables locales uniquement pour éviter de faire claquer des variables globales et polluer l'Espace nom mondial
  • les deux chemins de répertoire N'ont pas besoin d'exister (exigence pour ma demande)
  • les noms de chemin peuvent contenir des espaces, des caractères spéciaux, le contrôle caractère, les barres obliques inverses, les onglets, ', ", ?, *, [,], etc.
  • Core function "relPath" utilise les builtins de shell POSIX seulement mais nécessite canonique absolu chemins de répertoire en tant que paramètres
  • fonction étendue "relpath" peut gérer des chemins de répertoires arbitraires (aussi relatif, non canonique) mais nécessite un utilitaire externe GNU core "readlink "
  • a évité "echo" et utilisé "printf" à la place pour deux raisons:
  • pour éviter les conversions inutiles, les noms de chemins sont utilisés comme ils sont retournés et attendu par les utilitaires shell et OS (par exemple cd, ln, ls, find, mkdir); contrairement au " os " de python.chemin.relpath " qui interprétera un certain backslash sequences)
  • à L'exception des séquences de backslash mentionnées la dernière ligne de fonction "relPath" sorties pathnames compatibles avec python:

    path=$up${path#"$common"/}; path=${path%/}; printf %s "${path:-.}"
    

    la dernière ligne peut être remplacée (et simplifiée) par la ligne

    printf %s "$up${path#"$common"/}"
    

    je préfère ce dernier parce que

    1. les noms de fichiers peuvent être directement annexés à des chemins dir obtenus par relPath, par exemple:

      ln -s "$(relpath "<fromDir>" "<toDir>")<file>" "<fromDir>"
      
    2. les liens symboliques dans le même dir créés avec cette méthode n'ont pas le laid "./" est ajouté au nom du fichier.

  • si vous trouvez une erreur, veuillez contacter linuxball (at) gmail.com et j'essaierai remédier.
  • Added regression test suite (also POSIX shell compatible) ""

exemple de Code pour les tests de régression (il suffit d'ajouter le script shell):

############################################################################
# If called with 2 arguments assume they are dir paths and print rel. path #
############################################################################

test "$#" = 2 && {
    printf '%s\n' "Rel. path from '' to '' is '$(relpath "" "")'."
    exit 0
}

#######################################################
# If NOT called with 2 arguments run regression tests #
#######################################################

format="\t%-19s %-22s %-27s %-8s %-8s %-8s\n"
printf \
"\n\n*** Testing own and python's function with canonical absolute dirs\n\n"
printf "$format\n" \
    "From Directory" "To Directory" "Rel. Path" "relPath" "relpath" "python"
IFS=
while read -r p; do
    eval set -- $p
    case  in '#'*|'') continue;; esac # Skip comments and empty lines
    # q stores quoting character, use " if ' is used in path name
    q="'"; case  in *"'"*) q='"';; esac
    rPOk=passed rP=$(relPath "" ""); test "$rP" = "" || rPOk=$rP
    rpOk=passed rp=$(relpath "" ""); test "$rp" = "" || rpOk=$rp
    RPOk=passed
    RP=$(python -c "import os.path; print os.path.relpath($q$q, $q$q)")
    test "$RP" = "" || RPOk=$RP
    printf \
    "$format" "$q$q" "$q$q" "$q$q" "$q$rPOk$q" "$q$rpOk$q" "$q$RPOk$q"
done <<-"EOF"
    # From directory    To directory           Expected relative path

    '/'                 '/'                    '.'
    '/usr'              '/'                    '..'
    '/usr/'             '/'                    '..'
    '/'                 '/usr'                 'usr'
    '/'                 '/usr/'                'usr'
    '/usr'              '/usr'                 '.'
    '/usr/'             '/usr'                 '.'
    '/usr'              '/usr/'                '.'
    '/usr/'             '/usr/'                '.'
    '/u'                '/usr'                 '../usr'
    '/usr'              '/u'                   '../u'
    "/u'/dir"           "/u'/dir"              "."
    "/u'"               "/u'/dir"              "dir"
    "/u'/dir"           "/u'"                  ".."
    "/"                 "/u'/dir"              "u'/dir"
    "/u'/dir"           "/"                    "../.."
    "/u'"               "/u'"                  "."
    "/"                 "/u'"                  "u'"
    "/u'"               "/"                    ".."
    '/u"/dir'           '/u"/dir'              '.'
    '/u"'               '/u"/dir'              'dir'
    '/u"/dir'           '/u"'                  '..'
    '/'                 '/u"/dir'              'u"/dir'
    '/u"/dir'           '/'                    '../..'
    '/u"'               '/u"'                  '.'
    '/'                 '/u"'                  'u"'
    '/u"'               '/'                    '..'
    '/u /dir'           '/u /dir'              '.'
    '/u '               '/u /dir'              'dir'
    '/u /dir'           '/u '                  '..'
    '/'                 '/u /dir'              'u /dir'
    '/u /dir'           '/'                    '../..'
    '/u '               '/u '                  '.'
    '/'                 '/u '                  'u '
    '/u '               '/'                    '..'
    '/u\n/dir'          '/u\n/dir'             '.'
    '/u\n'              '/u\n/dir'             'dir'
    '/u\n/dir'          '/u\n'                 '..'
    '/'                 '/u\n/dir'             'u\n/dir'
    '/u\n/dir'          '/'                    '../..'
    '/u\n'              '/u\n'                 '.'
    '/'                 '/u\n'                 'u\n'
    '/u\n'              '/'                    '..'

    '/    a   b/å/⮀*/!' '/    a   b/å/⮀/xäå/?' '../../⮀/xäå/?'
    '/'                 '/A'                   'A'
    '/A'                '/'                    '..'
    '/  & /  !/*/\/E'  '/'                    '../../../../..'
    '/'                 '/  & /  !/*/\/E'     '  & /  !/*/\/E'
    '/  & /  !/*/\/E'  '/  & /  !/?/\/E/F'   '../../../?/\/E/F'
    '/X/Y'              '/  & /  !/C/\/E/F'   '../../  & /  !/C/\/E/F'
    '/  & /  !/C'       '/A'                   '../../../A'
    '/A /  !/C'         '/A /B'                '../../B'
    '/Â/  !/C'          '/Â/  !/C'             '.'
    '/  & /B / C'       '/  & /B / C/D'        'D'
    '/  & /  !/C'       '/  & /  !/C/\/Ê'     '\/Ê'
    '/Å/  !/C'          '/Å/  !/D'             '../D'
    '/.A /*B/C'         '/.A /*B/\/E'         '../\/E'
    '/  & /  !/C'       '/  & /D'              '../../D'
    '/  & /  !/C'       '/  & /\/E'           '../../\/E'
    '/  & /  !/C'       '/\/E/F'              '../../../\/E/F'
    '/home/p1/p2'       '/home/p1/p3'          '../p3'
    '/home/p1/p2'       '/home/p4/p5'          '../../p4/p5'
    '/home/p1/p2'       '/work/p6/p7'          '../../../work/p6/p7'
    '/home/p1'          '/work/p1/p2/p3/p4'    '../../work/p1/p2/p3/p4'
    '/home'             '/work/p2/p3'          '../work/p2/p3'
    '/'                 '/work/p2/p3/p4'       'work/p2/p3/p4'
    '/home/p1/p2'       '/home/p1/p2/p3/p4'    'p3/p4'
    '/home/p1/p2'       '/home/p1/p2/p3'       'p3'
    '/home/p1/p2'       '/home/p1/p2'          '.'
    '/home/p1/p2'       '/home/p1'             '..'
    '/home/p1/p2'       '/home'                '../..'
    '/home/p1/p2'       '/'                    '../../..'
    '/home/p1/p2'       '/work'                '../../../work'
    '/home/p1/p2'       '/work/p1'             '../../../work/p1'
    '/home/p1/p2'       '/work/p1/p2'          '../../../work/p1/p2'
    '/home/p1/p2'       '/work/p1/p2/p3'       '../../../work/p1/p2/p3'
    '/home/p1/p2'       '/work/p1/p2/p3/p4'    '../../../work/p1/p2/p3/p4'

    '/-'                '/-'                   '.'
    '/?'                '/?'                   '.'
    '/??'               '/??'                  '.'
    '/???'              '/???'                 '.'
    '/?*'               '/?*'                  '.'
    '/*'                '/*'                   '.'
    '/*'                '/**'                  '../**'
    '/*'                '/***'                 '../***'
    '/*.*'              '/*.**'                '../*.**'
    '/*.???'            '/*.??'                '../*.??'
    '/[]'               '/[]'                  '.'
    '/[a-z]*'           '/[0-9]*'              '../[0-9]*'
EOF


format="\t%-19s %-22s %-27s %-8s %-8s\n"
printf "\n\n*** Testing own and python's function with arbitrary dirs\n\n"
printf "$format\n" \
    "From Directory" "To Directory" "Rel. Path" "relpath" "python"
IFS=
while read -r p; do
    eval set -- $p
    case  in '#'*|'') continue;; esac # Skip comments and empty lines
    # q stores quoting character, use " if ' is used in path name
    q="'"; case  in *"'"*) q='"';; esac
    rpOk=passed rp=$(relpath "" ""); test "$rp" = "" || rpOk=$rp
    RPOk=passed
    RP=$(python -c "import os.path; print os.path.relpath($q$q, $q$q)")
    test "$RP" = "" || RPOk=$RP
    printf "$format" "$q$q" "$q$q" "$q$q" "$q$rpOk$q" "$q$RPOk$q"
done <<-"EOF"
    # From directory    To directory           Expected relative path

    'usr/p1/..//./p4'   'p3/../p1/p6/.././/p2' '../../p1/p2'
    './home/../../work' '..//././../dir///'    '../../dir'

    'home/p1/p2'        'home/p1/p3'           '../p3'
    'home/p1/p2'        'home/p4/p5'           '../../p4/p5'
    'home/p1/p2'        'work/p6/p7'           '../../../work/p6/p7'
    'home/p1'           'work/p1/p2/p3/p4'     '../../work/p1/p2/p3/p4'
    'home'              'work/p2/p3'           '../work/p2/p3'
    '.'                 'work/p2/p3'           'work/p2/p3'
    'home/p1/p2'        'home/p1/p2/p3/p4'     'p3/p4'
    'home/p1/p2'        'home/p1/p2/p3'        'p3'
    'home/p1/p2'        'home/p1/p2'           '.'
    'home/p1/p2'        'home/p1'              '..'
    'home/p1/p2'        'home'                 '../..'
    'home/p1/p2'        '.'                    '../../..'
    'home/p1/p2'        'work'                 '../../../work'
    'home/p1/p2'        'work/p1'              '../../../work/p1'
    'home/p1/p2'        'work/p1/p2'           '../../../work/p1/p2'
    'home/p1/p2'        'work/p1/p2/p3'        '../../../work/p1/p2/p3'
    'home/p1/p2'        'work/p1/p2/p3/p4'     '../../../work/p1/p2/p3/p4'
EOF
11
répondu linuxball 2017-05-23 10:31:35

présumant que vous avez installé: bash, pwd, dirname, echo; puis relpath est

#!/bin/bash
s=$(cd ${1%%/};pwd); d=$(cd ;pwd); while [ "${d#$s/}" == "${d}" ]
do s=$(dirname $s);b="../${b}"; done; echo ${b}${d#$s/}

j'ai ont joué au golf la réponse de pini et de quelques autres idées

10
répondu Alexx Roche 2017-05-23 12:26:37

ce script donne des résultats corrects seulement pour les entrées qui sont des chemins absolus ou des chemins relatifs sans . ou .. :

#!/bin/bash

# usage: relpath from to

if [[ "" == "" ]]
then
    echo "."
    exit
fi

IFS="/"

current=()
absolute=()

abssize=${#absolute[@]}
cursize=${#current[@]}

while [[ ${absolute[level]} == ${current[level]} ]]
do
    (( level++ ))
    if (( level > abssize || level > cursize ))
    then
        break
    fi
done

for ((i = level; i < cursize; i++))
do
    if ((i > level))
    then
        newpath=$newpath"/"
    fi
    newpath=$newpath".."
done

for ((i = level; i < abssize; i++))
do
    if [[ -n $newpath ]]
    then
        newpath=$newpath"/"
    fi
    newpath=$newpath${absolute[i]}
done

echo "$newpath"
6
répondu Dennis Williamson 2011-07-24 01:19:48

Je voudrais juste utiliser Perl pour cette tâche pas-si-triviale:

absolute="/foo/bar"
current="/foo/baz/foo"

# Perl is magic
relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel("'$absolute'","'$current'")')
6
répondu 2012-02-12 17:08:10

une légère amélioration sur kasku et Pini réponses, qui joue plus agréable avec les espaces et permet de passer des chemins relatifs:

#!/bin/bash
# both  and  are paths
# returns  relative to 
absolute=`readlink -f ""`
current=`readlink -f ""`
# Perl is magic
# Quoting horror.... spaces cause problems, that's why we need the extra " in here:
relative=$(perl -MFile::Spec -e "print File::Spec->abs2rel(q($absolute),q($current))")

echo $relative
6
répondu sinelaw 2017-05-23 12:26:37

peu de réponses ici sont pratiques pour une utilisation quotidienne. Comme il est très difficile de le faire correctement en pur bash, je suggère la solution suivante, fiable (semblable à une suggestion enfouie dans un commentaire):

function relpath() { 
  python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@";
}

ensuite, vous pouvez obtenir le chemin relatif basé sur le répertoire courant:

echo $(relpath somepath)

ou vous pouvez spécifier que le chemin est relatif à un répertoire donné:

echo $(relpath somepath /etc)  # relative to /etc

le un inconvénient est que cela nécessite python, mais:

  • il fonctionne de manière identique dans n'importe quel python > = 2.6
  • il n'est pas nécessaire que les fichiers ou répertoires existent.
  • Les noms de fichiers
  • peuvent contenir un plus grand nombre de caractères spéciaux. Par exemple, beaucoup d'autres solutions ne fonctionnent pas si les noms de fichiers contiennent espaces ou autres caractères spéciaux.
  • c'est une fonction d'une ligne qui n'encombre pas les scripts.

noter que les solutions qui comprennent basename ou dirname ne sont pas nécessairement meilleures, car elles exigent que coreutils soit installé. Si quelqu'un a une solution pure bash qui est fiable et simple (plutôt qu'une curiosité alambiquée), je serais surpris.

4
répondu Gary Wisniewski 2015-07-29 07:09:20

malheureusement, la réponse de Mark Rushakoff (maintenant supprimé-il Référencé le code de ici ) ne semble pas fonctionner correctement lorsqu'adapté à:

source=/home/part2/part3/part4
target=/work/proj1/proj2

la pensée décrite dans le commentaire peut être affinée pour qu'elle fonctionne 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 aller), et que soit les deux sont des noms de chemin absolus, soit les deux sont Relatif. Si l'un est absolu et l'autre relatif, la chose la plus facile est de préfixer le nom relatif avec le répertoire de travail courant - mais le code ci-dessous ne fait pas cela.


attention

le code ci-dessous est proche de fonctionner correctement, mais n'est pas tout à fait correct.

  1. le problème est abordé dans les commentaires de Dennis Williamson.
  2. Il ya aussi un problème que ce traitement purement textuel de noms de chemins et vous pouvez être sérieusement perturbé par des liens symboliques bizarres.
  3. le code ne gère pas les 'points' errants dans les chemins comme ' xyz/./pqr ".
  4. le code ne gère pas les 'doubles points' errants dans les chemins comme ' xyz/../pqr ".
  5. trivialement: le code ne supprime pas la mention ./ " des chemins.

le code de Dennis est meilleur car il fixe 1 et 5, mais a la même questions 2, 3, 4. Utilisez le code de Dennis (et votez-le avant cela) pour cette raison.

(NB: POSIX fournit un appel système realpath() qui résout les noms de chemin 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 enveloppe realpath() - 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. Ainsi, étant donné deux noms compatibles, les diviser chacun en composants:

  • définit le chemin relatif à vide.
  • Alors que les composants sont les mêmes, passez à la suivante.
  • Lorsque les composants correspondants sont différents ou il n'y a plus de composants pour un chemin:
  • s'il ne reste aucun composant source et que le chemin relatif est vide, ajouter"."au début.
  • pour chaque composant source restant, préfixer le chemin relatif avec"../".
  • s'il ne reste aucun composant cible et que le chemin relatif est vide, ajouter"."au début.
  • Pour chaque composant cible, ajouter le composant à la fin de le chemin après un slash.

ainsi:

#!/bin/perl -w

use strict;

# Should fettle the arguments if one is absolute and one relative:
# Oops - missing functionality!

# Split!
my(@source) = split '/', $ARGV[0];
my(@target) = split '/', $ARGV[1];

my $count = scalar(@source);
   $count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";

my $i;
for ($i = 0; $i < $count; $i++)
{
    last if $source[$i] ne $target[$i];
}

$relpath = "." if ($i >= scalar(@source) && $relpath eq "");
for (my $s = $i; $s < scalar(@source); $s++)
{
    $relpath = "../$relpath";
}
$relpath = "." if ($i >= scalar(@target) && $relpath eq "");
for (my $t = $i; $t < scalar(@target); $t++)
{
    $relpath .= "/$target[$t]";
}

# Clean up result (remove double slash, trailing slash, trailing slash-dot).
$relpath =~ s%//%/%;
$relpath =~ s%/$%%;
$relpath =~ s%/\.$%%;

print "source  = $ARGV[0]\n";
print "target  = $ARGV[1]\n";
print "relpath = $relpath\n";

script de Test (les crochets contiennent un vide et un onglet):

sed 's/#.*//;/^[    ]*$/d' <<! |

/home/part1/part2 /home/part1/part3
/home/part1/part2 /home/part4/part5
/home/part1/part2 /work/part6/part7
/home/part1       /work/part1/part2/part3/part4
/home             /work/part2/part3
/                 /work/part2/part3/part4

/home/part1/part2 /home/part1/part2/part3/part4
/home/part1/part2 /home/part1/part2/part3
/home/part1/part2 /home/part1/part2
/home/part1/part2 /home/part1
/home/part1/part2 /home
/home/part1/part2 /

/home/part1/part2 /work
/home/part1/part2 /work/part1
/home/part1/part2 /work/part1/part2
/home/part1/part2 /work/part1/part2/part3
/home/part1/part2 /work/part1/part2/part3/part4

home/part1/part2 home/part1/part3
home/part1/part2 home/part4/part5
home/part1/part2 work/part6/part7
home/part1       work/part1/part2/part3/part4
home             work/part2/part3
.                work/part2/part3

home/part1/part2 home/part1/part2/part3/part4
home/part1/part2 home/part1/part2/part3
home/part1/part2 home/part1/part2
home/part1/part2 home/part1
home/part1/part2 home
home/part1/part2 .

home/part1/part2 work
home/part1/part2 work/part1
home/part1/part2 work/part1/part2
home/part1/part2 work/part1/part2/part3
home/part1/part2 work/part1/part2/part3/part4

!

while read source target
do
    perl relpath.pl $source $target
    echo
done

sortie du script de test:

source  = /home/part1/part2
target  = /home/part1/part3
relpath = ../part3

source  = /home/part1/part2
target  = /home/part4/part5
relpath = ../../part4/part5

source  = /home/part1/part2
target  = /work/part6/part7
relpath = ../../../work/part6/part7

source  = /home/part1
target  = /work/part1/part2/part3/part4
relpath = ../../work/part1/part2/part3/part4

source  = /home
target  = /work/part2/part3
relpath = ../work/part2/part3

source  = /
target  = /work/part2/part3/part4
relpath = ./work/part2/part3/part4

source  = /home/part1/part2
target  = /home/part1/part2/part3/part4
relpath = ./part3/part4

source  = /home/part1/part2
target  = /home/part1/part2/part3
relpath = ./part3

source  = /home/part1/part2
target  = /home/part1/part2
relpath = .

source  = /home/part1/part2
target  = /home/part1
relpath = ..

source  = /home/part1/part2
target  = /home
relpath = ../..

source  = /home/part1/part2
target  = /
relpath = ../../../..

source  = /home/part1/part2
target  = /work
relpath = ../../../work

source  = /home/part1/part2
target  = /work/part1
relpath = ../../../work/part1

source  = /home/part1/part2
target  = /work/part1/part2
relpath = ../../../work/part1/part2

source  = /home/part1/part2
target  = /work/part1/part2/part3
relpath = ../../../work/part1/part2/part3

source  = /home/part1/part2
target  = /work/part1/part2/part3/part4
relpath = ../../../work/part1/part2/part3/part4

source  = home/part1/part2
target  = home/part1/part3
relpath = ../part3

source  = home/part1/part2
target  = home/part4/part5
relpath = ../../part4/part5

source  = home/part1/part2
target  = work/part6/part7
relpath = ../../../work/part6/part7

source  = home/part1
target  = work/part1/part2/part3/part4
relpath = ../../work/part1/part2/part3/part4

source  = home
target  = work/part2/part3
relpath = ../work/part2/part3

source  = .
target  = work/part2/part3
relpath = ../work/part2/part3

source  = home/part1/part2
target  = home/part1/part2/part3/part4
relpath = ./part3/part4

source  = home/part1/part2
target  = home/part1/part2/part3
relpath = ./part3

source  = home/part1/part2
target  = home/part1/part2
relpath = .

source  = home/part1/part2
target  = home/part1
relpath = ..

source  = home/part1/part2
target  = home
relpath = ../..

source  = home/part1/part2
target  = .
relpath = ../../..

source  = home/part1/part2
target  = work
relpath = ../../../work

source  = home/part1/part2
target  = work/part1
relpath = ../../../work/part1

source  = home/part1/part2
target  = work/part1/part2
relpath = ../../../work/part1/part2

source  = home/part1/part2
target  = work/part1/part2/part3
relpath = ../../../work/part1/part2/part3

source  = home/part1/part2
target  = work/part1/part2/part3/part4
relpath = ../../../work/part1/part2/part3/part4

ce script Perl fonctionne assez bien sur Unix (il ne tient pas compte de toutes les complexités des noms de chemins Windows) face à des entrées bizarres. Il utilise le module Cwd et sa fonction realpath pour résoudre le chemin réel 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:

source   = home/part1/part2
target   = .
relpath1 = ../../..
relpath2 = ../../../.

Les deux résultats sont équivalents mais pas identiques. (La sortie est une version légèrement modifiée du script de test - le script Perl ci-dessous affiche simplement la réponse, plutôt que les entrées et la réponse comme dans le script ci-dessus.) Maintenant: dois-je éliminer le travail non-réponse? Peut-être...

#!/bin/perl -w
# Based loosely on code from: http://unix.derkeiler.com/Newsgroups/comp.unix.shell/2005-10/1256.html
# Via: /q/convert-absolute-path-into-relative-path-given-a-current-directory-using-bash-20104/"Usage: "151950920" from to\n" if scalar @ARGV != 2;

use Cwd qw(realpath getcwd);

my $pwd;
my $verbose = 0;

# Fettle filename so it is absolute.
# Deals with '//', '/./' and '/../' notations, plus symlinks.
# The realpath() function does the hard work if the path exists.
# For non-existent paths, the code does a purely textual hack.
sub resolve
{
    my($name) = @_;
    my($path) = realpath($name);
    if (!defined $path)
    {
        # Path does not exist - do the best we can with lexical analysis
        # Assume Unix - not dealing with Windows.
        $path = $name;
        if ($name !~ m%^/%)
        {
            $pwd = getcwd if !defined $pwd;
            $path = "$pwd/$path";
        }
        $path =~ s%//+%/%g;     # Not UNC paths.
        $path =~ s%/$%%;        # No trailing /
        $path =~ s%/\./%/%g;    # No embedded /./
        # Try to eliminate /../abc/
        $path =~ s%/\.\./(?:[^/]+)(/|$)%%g;
        $path =~ s%/\.$%%;      # No trailing /.
        $path =~ s%^\./%%;      # No leading ./
        # What happens with . and / as inputs?
    }
    return($path);
}

sub print_result
{
    my($source, $target, $relpath) = @_;
    if ($verbose)
    {
        print "source  = $ARGV[0]\n";
        print "target  = $ARGV[1]\n";
        print "relpath = $relpath\n";
    }
    else
    {
        print "$relpath\n";
    }
    exit 0;
}

my($source) = resolve($ARGV[0]);
my($target) = resolve($ARGV[1]);
print_result($source, $target, ".") if ($source eq $target);

# Split!
my(@source) = split '/', $source;
my(@target) = split '/', $target;

my $count = scalar(@source);
   $count = scalar(@target) if (scalar(@target) < $count);
my $relpath = "";
my $i;

# Both paths are absolute; Perl splits an empty field 0.
for ($i = 1; $i < $count; $i++)
{
    last if $source[$i] ne $target[$i];
}

for (my $s = $i; $s < scalar(@source); $s++)
{
    $relpath = "$relpath/" if ($s > $i);
    $relpath = "$relpath..";
}
for (my $t = $i; $t < scalar(@target); $t++)
{
    $relpath = "$relpath/" if ($relpath ne "");
    $relpath = "$relpath$target[$t]";
}

print_result($source, $target, $relpath);
3
répondu Jonathan Leffler 2010-04-04 03:22:53

test.sh:

#!/bin/bash                                                                 

cd /home/ubuntu
touch blah
TEST=/home/ubuntu/.//blah
echo TEST=$TEST
TMP=$(readlink -e "$TEST")
echo TMP=$TMP
REL=${TMP#$(pwd)/}
echo REL=$REL

test:

$ ./test.sh 
TEST=/home/ubuntu/.//blah
TMP=/home/ubuntu/blah
REL=blah
3
répondu Steve 2010-06-11 17:07:41

j'ai pris votre question comme un défi pour écrire ceci en code shell "portable", i.e.

  • avec un shell POSIX dans l'esprit
  • pas de bashisms comme des tableaux
  • évitez d'appeler les externes comme la peste. Il n'y a pas une seule fourchette dans le script! Cela le rend incroyablement rapide, surtout sur les systèmes avec fourche importante au-dessus, comme cygwin.
  • Doivent composer avec glob caractères dans les noms de fichiers (*, ?, [,])

il fonctionne sur n'importe quelle coque conforme POSIX (zsh, bash, ksh, ash, busybox, ...). Il contient même une suite de tests pour vérifier son fonctionnement. La canonisation de pathnames est laissée comme un exercice. :- )

#!/bin/sh

# Find common parent directory path for a pair of paths.
# Call with two pathnames as args, e.g.
# commondirpart foo/bar foo/baz/bat -> result="foo/"
# The result is either empty or ends with "/".
commondirpart () {
   result=""
   while test ${#1} -gt 0 -a ${#2} -gt 0; do
      if test "${1%${1#?}}" != "${2%${2#?}}"; then   # First characters the same?
         break                                       # No, we're done comparing.
      fi
      result="$result${1%${1#?}}"                    # Yes, append to result.
      set -- "${1#?}" "${2#?}"                       # Chop first char off both strings.
   done
   case "$result" in
   (""|*/) ;;
   (*)     result="${result%/*}/";;
   esac
}

# Turn foo/bar/baz into ../../..
#
dir2dotdot () {
   OLDIFS="$IFS" IFS="/" result=""
   for dir in ; do
      result="$result../"
   done
   result="${result%/}"
   IFS="$OLDIFS"
}

# Call with FROM TO args.
relativepath () {
   case "" in
   (*//*|*/./*|*/../*|*?/|*/.|*/..)
      printf '%s\n' "'' not canonical"; exit 1;;
   (/*)
      from="${1#?}";;
   (*)
      printf '%s\n' "'' not absolute"; exit 1;;
   esac
   case "" in
   (*//*|*/./*|*/../*|*?/|*/.|*/..)
      printf '%s\n' "'' not canonical"; exit 1;;
   (/*)
      to="${2#?}";;
   (*)
      printf '%s\n' "'' not absolute"; exit 1;;
   esac

   case "$to" in
   ("$from")   # Identical directories.
      result=".";;
   ("$from"/*) # From /x to /x/foo/bar -> foo/bar
      result="${to##$from/}";;
   ("")        # From /foo/bar to / -> ../..
      dir2dotdot "$from";;
   (*)
      case "$from" in
      ("$to"/*)       # From /x/foo/bar to /x -> ../..
         dir2dotdot "${from##$to/}";;
      (*)             # Everything else.
         commondirpart "$from" "$to"
         common="$result"
         dir2dotdot "${from#$common}"
         result="$result/${to#$common}"
      esac
      ;;
   esac
}

set -f # noglob

set -x
cat <<EOF |
/ / .
/- /- .
/? /? .
/?? /?? .
/??? /??? .
/?* /?* .
/* /* .
/* /** ../**
/* /*** ../***
/*.* /*.** ../*.**
/*.??? /*.?? ../*.??
/[] /[] .
/[a-z]* /[0-9]* ../[0-9]*
/foo /foo .
/foo / ..
/foo/bar / ../..
/foo/bar /foo ..
/foo/bar /foo/baz ../baz
/foo/bar /bar/foo  ../../bar/foo
/foo/bar/baz /gnarf/blurfl/blubb ../../../gnarf/blurfl/blubb
/foo/bar/baz /gnarf ../../../gnarf
/foo/bar/baz /foo/baz ../../baz
/foo. /bar. ../bar.
EOF
while read FROM TO VIA; do
   relativepath "$FROM" "$TO"
   printf '%s\n' "FROM: $FROM" "TO:   $TO" "VIA:  $result"
   if test "$result" != "$VIA"; then
      printf '%s\n' "OOOPS! Expected '$VIA' but got '$result'"
   fi
done

# vi: set tabstop=3 shiftwidth=3 expandtab fileformat=unix :
3
répondu Jens 2011-08-11 11:42:15

Ma Solution:

computeRelativePath() 
{

    Source=$(readlink -f )
    Target=$(readlink -f )

    local OLDIFS=$IFS
    IFS="/"

    local SourceDirectoryArray=($Source)
    local TargetDirectoryArray=($Target)

    local SourceArrayLength=$(echo ${SourceDirectoryArray[@]} | wc -w)
    local TargetArrayLength=$(echo ${TargetDirectoryArray[@]} | wc -w)

    local Length
    test $SourceArrayLength -gt $TargetArrayLength && Length=$SourceArrayLength || Length=$TargetArrayLength


    local Result=""
    local AppendToEnd=""

    IFS=$OLDIFS

    local i

    for ((i = 0; i <= $Length + 1 ; i++ ))
    do
            if [ "${SourceDirectoryArray[$i]}" = "${TargetDirectoryArray[$i]}" ]
            then
                continue    
            elif [ "${SourceDirectoryArray[$i]}" != "" ] && [ "${TargetDirectoryArray[$i]}" != "" ] 
            then
                AppendToEnd="${AppendToEnd}${TargetDirectoryArray[${i}]}/"
                Result="${Result}../"               

            elif [ "${SourceDirectoryArray[$i]}" = "" ]
            then
                Result="${Result}${TargetDirectoryArray[${i}]}/"
            else
                Result="${Result}../"
            fi
    done

    Result="${Result}${AppendToEnd}"

    echo $Result

}
2
répondu Anonymous 2011-10-25 16:37:43

voici ma version. Il est basé sur la réponse par @Offirmo . Je l'ai rendu compatible avec le tableau de bord et j'ai corrigé la défaillance suivante du testcase:

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

# both  and  are absolute paths beginning with /
# returns relative path to /$target from /$source
CT_FindRelativePath()
{
    local insource=
    local intarget=

    # Ensure both source and target end with /
    # This simplifies the inner loop.
    #echo "insource : \"$insource\""
    #echo "intarget : \"$intarget\""
    case "$insource" in
        */) ;;
        *) source="$insource"/ ;;
    esac

    case "$intarget" in
        */) ;;
        *) target="$intarget"/ ;;
    esac

    #echo "source : \"$source\""
    #echo "target : \"$target\""

    local common_part=$source # for now

    local result=""

    #echo "common_part is now : \"$common_part\""
    #echo "result is now      : \"$result\""
    #echo "target#common_part : \"${target#$common_part}\""
    while [ "${target#$common_part}" = "${target}" -a "${common_part}" != "//" ]; 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
        if [ -z "${result}" ]; then
            result="../"
        else
            result="../$result"
        fi
        #echo "(w) common_part is now : \"$common_part\""
        #echo "(w) result is now      : \"$result\""
        #echo "(w) target#common_part : \"${target#$common_part}\""
    done

    #echo "(f) common_part is     : \"$common_part\""

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

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

    if [ -n "${result}" -a -n "${forward_part}" ]; then
        #echo "(simple concat)"
        result="$result$forward_part"
    elif [ -n "${forward_part}" ]; then
        result="$forward_part"
    fi
    #echo "result = \"$result\""

    # if a / was added to target and result ends in / then remove it now.
    if [ "$intarget" != "$target" ]; then
        case "$result" in
            */) result=$(echo "$result" | awk '{ string=substr("151900920", 1, length("151900920")-1); print string; }' ) ;;
        esac
    fi

    echo $result

    return 0
}
2
répondu Ray Donnelly 2017-05-23 12:34:47

je suppose que celui-ci fera l'affaire aussi... (livré avec des tests intégrés) :)

OK, un peu de frais généraux attendus, mais nous faisons Bourne shell ici! ;)

#!/bin/sh

#
# Finding the relative path to a certain file (), given the absolute path ()
# (available here too http://pastebin.com/tWWqA8aB)
#
relpath () {
  local  FROM=""
  local    TO="`dirname  `"
  local  FILE="`basename `"
  local  DEBUG=""

  local FROMREL=""
  local FROMUP="$FROM"
  while [ "$FROMUP" != "/" ]; do
    local TOUP="$TO"
    local TOREL=""
    while [ "$TOUP" != "/" ]; do
      [ -z "$DEBUG" ] || echo 1>&2 "$DEBUG$FROMUP =?= $TOUP"
      if [ "$FROMUP" = "$TOUP" ]; then
        echo "${FROMREL:-.}/$TOREL${TOREL:+/}$FILE"
        return 0
      fi
      TOREL="`basename $TOUP`${TOREL:+/}$TOREL"
      TOUP="`dirname $TOUP`"
    done
    FROMREL="..${FROMREL:+/}$FROMREL"
    FROMUP="`dirname $FROMUP`"
  done
  echo "${FROMREL:-.}${TOREL:+/}$TOREL/$FILE"
  return 0
}

relpathshow () {
  echo " - target "
  echo "   from   "
  echo "   ------"
  echo "   => `relpath   '      '`"
  echo ""
}

# If given 2 arguments, do as said...
if [ -n "" ]; then
  relpath  

# If only one given, then assume current directory
elif [ -n "" ]; then
  relpath `pwd` 

# Otherwise perform a set of built-in tests to confirm the validity of the method! ;)
else

  relpathshow /usr/share/emacs22/site-lisp/emacs-goodies-el \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/share/emacs23/site-lisp/emacs-goodies-el \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/bin \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/bin \
              /usr/share/emacs22/site-lisp/emacs-goodies-el/filladapt.el

  relpathshow /usr/bin/share/emacs22/site-lisp/emacs-goodies-el \
              /etc/motd

  relpathshow / \
              /initrd.img
fi
1
répondu D4rk1B0t 2011-12-23 23:05:18

ce script ne fonctionne que sur les noms de chemins. Il ne nécessite pas de tous les fichiers pour exister. Si les chemins d'accès transmis 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 il se peut qu'il ne soit pas portable.

#!/bin/bash
set -e
declare SCRIPT_NAME="$(basename "151900920")"
function usage {
    echo "Usage: $SCRIPT_NAME <base path> <target file>"
    echo "       Outputs <target file> relative to <base path>"
    exit 1
}

if [ $# -lt 2 ]; then usage; fi

declare base=
declare target=
declare -a base_part=()
declare -a target_part=()

#Split path elements & canonicalize
OFS="$IFS"; IFS='/'
bpl=0;
for bp in $base; do
    case "$bp" in
        ".");;
        "..") let "bpl=$bpl-1" ;;
        *) base_part[${bpl}]="$bp" ; let "bpl=$bpl+1";;
    esac
done
tpl=0;
for tp in $target; do
    case "$tp" in
        ".");;
        "..") let "tpl=$tpl-1" ;;
        *) target_part[${tpl}]="$tp" ; let "tpl=$tpl+1";;
    esac
done
IFS="$OFS"

#Count common prefix
common=0
for (( i=0 ; i<$bpl ; i++ )); do
    if [ "${base_part[$i]}" = "${target_part[$common]}" ] ; then
        let "common=$common+1"
    else
        break
    fi
done

#Compute number of directories up
let "updir=$bpl-$common" || updir=0 #if the expression is zero, 'let' fails

#trivial case (after canonical decomposition)
if [ $updir -eq 0 ]; then
    echo .
    exit
fi

#Print updirs
for (( i=0 ; i<$updir ; i++ )); do
    echo -n ../
done

#Print remaining path
for (( i=$common ; i<$tpl ; i++ )); do
    if [ $i -ne $common ]; then
        echo -n "/"
    fi
    if [ "" != "${target_part[$i]}" ] ; then
        echo -n "${target_part[$i]}"
    fi
done
#One last newline
echo
1
répondu juancn 2015-09-27 21:09:54

cette réponse ne répond pas à la partie Bash de la question, mais parce que j'ai essayé d'utiliser les réponses dans cette question pour mettre en œuvre cette fonctionnalité dans Emacs je vais le jeter là-bas.

Emacs a en fait une fonction pour cela:

ELISP> (file-relative-name "/a/b/c" "/a/b/c")
"."
ELISP> (file-relative-name "/a/b/c" "/a/b")
"c"
ELISP> (file-relative-name "/a/b/c" "/c/b")
"../../a/b/c"
0
répondu fakedrake 2015-09-27 21:16:55

voici un script shell qui le fait sans appeler d'autres programmes:

#! /bin/env bash 

#bash script to find the relative path between two directories

mydir=${0%/}
mydir=${0%/*}
creadlink="$mydir/creadlink"

shopt -s extglob

relpath_ () {
        path1=$("$creadlink" "")
        path2=$("$creadlink" "")
        orig1=$path1
        path1=${path1%/}/
        path2=${path2%/}/

        while :; do
                if test ! "$path1"; then
                        break
                fi
                part1=${path2#$path1}
                if test "${part1#/}" = "$part1"; then
                        path1=${path1%/*}
                        continue
                fi
                if test "${path2#$path1}" = "$path2"; then
                        path1=${path1%/*}
                        continue
                fi
                break
        done
        part1=$path1
        path1=${orig1#$part1}
        depth=${path1//+([^\/])/..}
        path1=${path2#$path1}
        path1=${depth}${path2#$part1}
        path1=${path1##+(\/)}
        path1=${path1%/}
        if test ! "$path1"; then
                path1=.
        fi
        printf "$path1"

}

relpath_test () {
        res=$(relpath_ /path1/to/dir1 /path1/to/dir2 )
        expected='../dir2'
        test_results "$res" "$expected"

        res=$(relpath_ / /path1/to/dir2 )
        expected='path1/to/dir2'
        test_results "$res" "$expected"

        res=$(relpath_ /path1/to/dir2 / )
        expected='../../..'
        test_results "$res" "$expected"

        res=$(relpath_ / / )
        expected='.'
        test_results "$res" "$expected"

        res=$(relpath_ /path/to/dir2/dir3 /path/to/dir1/dir4/dir4a )
        expected='../../dir1/dir4/dir4a'
        test_results "$res" "$expected"

        res=$(relpath_ /path/to/dir1/dir4/dir4a /path/to/dir2/dir3 )
        expected='../../../dir2/dir3'
        test_results "$res" "$expected"

        #res=$(relpath_ . /path/to/dir2/dir3 )
        #expected='../../../dir2/dir3'
        #test_results "$res" "$expected"
}

test_results () {
        if test ! "" = ""; then
                printf 'failed!\nresult:\nX%sX\nexpected:\nX%sX\n\n' "$@"
        fi
}

#relpath_test

source: http://www.ynform.org/w/Pub/Relpath

-1
répondu Poor Yorick 2011-09-08 13:05:01

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 A-P pour ça. Un fragment de mon script est ajouté. C'est dans une fonction dans un script shell, donc les $1 et $2. La valeur de résultat, qui est le chemin relatif de START_ABS à END_ABS, est dans la variable UPDIRS. Le cd de script se trouve dans chaque répertoire de paramètres afin d'exécuter le pwd-P et cela signifie également que les paramètres relatifs de chemin sont manipulés. Santé, Jim

SAVE_DIR="$PWD"
cd ""
START_ABS=`pwd -P`
cd "$SAVE_DIR"
cd ""
END_ABS=`pwd -P`

START_WORK="$START_ABS"
UPDIRS=""

while test -n "${START_WORK}" -a "${END_ABS/#${START_WORK}}" '==' "$END_ABS";
do
    START_WORK=`dirname "$START_WORK"`"/"
    UPDIRS=${UPDIRS}"../"
done
UPDIRS="$UPDIRS${END_ABS/#${START_WORK}}"
cd "$SAVE_DIR"
-1
répondu sjjh 2014-05-29 12:43:19

encore une autre solution, pure bash + GNU readlink pour une utilisation facile dans le contexte suivant:

ln -s "$(relpath "$A" "$B")" "$B"

Edit: assurez-vous que "$B" soit n'existe pas ou n'softlink dans ce cas, sinon relpath suit ce lien, qui n'est pas ce que vous voulez!

cela fonctionne dans presque tous les Linux actuels. Si readlink -m ne fonctionne pas à vos côtés, essayez readlink -f à la place. Voir aussi https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 pour les mises à jour possibles:

: relpath A B
# Calculate relative path from A to B, returns true on success
# Example: ln -s "$(relpath "$A" "$B")" "$B"
relpath()
{
local X Y A
# We can create dangling softlinks
X="$(readlink -m -- "")" || return
Y="$(readlink -m -- "")" || return
X="${X%/}/"
A=""
while   Y="${Y%/*}"
        [ ".${X#"$Y"/}" = ".$X" ]
do
        A="../$A"
done
X="$A${X#"$Y"/}"
X="${X%/}"
echo "${X:-.}"
}

Notes:

  • on a pris soin qu'il ne soit pas dangereux contre l'extension de méta-caractères non désirée de l'interpréteur de commandes, dans le cas où les noms de fichiers contiennent * ou ? .
  • la sortie est destinée à être utilisable comme premier argument à ln -s :
    • relpath / / donne . et non la chaîne vide
    • relpath a a donne a , même si a se trouve être un répertoire
  • la plupart des cas communs ont été testés pour donner des résultats raisonnables, aussi.
  • cette solution utilise le préfixe de chaîne correspondant, donc readlink est nécessaire pour canoniser les chemins.
  • grâce à readlink -m il fonctionne pour les chemins non encore existants, aussi.

Sur les anciens systèmes, où readlink -m n'est pas disponible, readlink -f échoue si le fichier n'existe pas. Donc, vous avez probablement besoin de quelques contournements comme celui-ci (non testé!):

readlink_missing()
{
readlink -m -- "" && return
readlink -f -- "" && return
[ -e . ] && echo "$(readlink_missing "$(dirname "")")/$(basename "")"
}

ce n'est pas vraiment tout à fait correct dans le cas comprend . ou .. pour les chemins non existants (comme dans /doesnotexist/./a ), mais il devrait couvrir la plupart des cas.

(remplacer readlink -m -- ci-dessus par readlink_missing .)

modifier en raison de la note descendante suit

voici un test, que cette fonction, en effet, est correcte:

check()
{
res="$(relpath "" "")"
[ ".$res" = "." ] && return
printf ':WRONG: %-10q %-10q gives %q\nCORRECT %-10q %-10q gives %q\n' "" "" "$res" "$@"
}

#     TARGET   SOURCE         RESULT
check "/A/B/C" "/A"           ".."
check "/A/B/C" "/A.x"         "../../A.x"
check "/A/B/C" "/A/B"         "."
check "/A/B/C" "/A/B/C"       "C"
check "/A/B/C" "/A/B/C/D"     "C/D"
check "/A/B/C" "/A/B/C/D/E"   "C/D/E"
check "/A/B/C" "/A/B/D"       "D"
check "/A/B/C" "/A/B/D/E"     "D/E"
check "/A/B/C" "/A/D"         "../D"
check "/A/B/C" "/A/D/E"       "../D/E"
check "/A/B/C" "/D/E/F"       "../../D/E/F"

check "/foo/baz/moo" "/foo/bar" "../bar"

perplexe? Eh bien, ce sont les bons résultats ! Même si vous pensez qu'il ne correspond pas à la question, voici la preuve que c'est correct:

check "http://example.com/foo/baz/moo" "http://example.com/foo/bar" "../bar"

sans aucun doute, ../bar est la trajectoire relative exacte et la seule correcte de la la page bar vu à partir de la page moo . Tout le reste serait simplement faux.

il est trivial d'adopter la sortie à la question qui suppose apparemment ,que current est un répertoire:

absolute="/foo/bar"
current="/foo/baz/foo"
relative="../$(relpath "$absolute" "$current")"

cela revient exactement, ce qui a été demandé.

et avant de lever un sourcil, voici une variante un peu plus complexe de relpath (spot la petite différence), qui devrait fonctionner pour la syntaxe URL-, aussi (ainsi, un / survit, grâce à quelque bash - magie):

# Calculate relative PATH to the given DEST from the given BASE
# In the URL case, both URLs must be absolute and have the same Scheme.
# The `SCHEME:` must not be present in the FS either.
# This way this routine works for file paths an
: relpathurl DEST BASE
relpathurl()
{
local X Y A
# We can create dangling softlinks
X="$(readlink -m -- "")" || return
Y="$(readlink -m -- "")" || return
X="${X%/}/${1#"${1%/}"}"
Y="${Y%/}${2#"${2%/}"}"
A=""
while   Y="${Y%/*}"
        [ ".${X#"$Y"/}" = ".$X" ]
do
        A="../$A"
done
X="$A${X#"$Y"/}"
X="${X%/}"
echo "${X:-.}"
}

Et voici les vérifications juste pour faire clair: Il fonctionne vraiment comme dit.

check()
{
res="$(relpathurl "" "")"
[ ".$res" = "." ] && return
printf ':WRONG: %-10q %-10q gives %q\nCORRECT %-10q %-10q gives %q\n' "" "" "$res" "$@"
}

#     TARGET   SOURCE         RESULT
check "/A/B/C" "/A"           ".."
check "/A/B/C" "/A.x"         "../../A.x"
check "/A/B/C" "/A/B"         "."
check "/A/B/C" "/A/B/C"       "C"
check "/A/B/C" "/A/B/C/D"     "C/D"
check "/A/B/C" "/A/B/C/D/E"   "C/D/E"
check "/A/B/C" "/A/B/D"       "D"
check "/A/B/C" "/A/B/D/E"     "D/E"
check "/A/B/C" "/A/D"         "../D"
check "/A/B/C" "/A/D/E"       "../D/E"
check "/A/B/C" "/D/E/F"       "../../D/E/F"

check "/foo/baz/moo" "/foo/bar" "../bar"
check "http://example.com/foo/baz/moo" "http://example.com/foo/bar" "../bar"

check "http://example.com/foo/baz/moo/" "http://example.com/foo/bar" "../../bar"
check "http://example.com/foo/baz/moo"  "http://example.com/foo/bar/" "../bar/"
check "http://example.com/foo/baz/moo/"  "http://example.com/foo/bar/" "../../bar/"

et voici comment ceci peut être utilisé pour donner le résultat voulu de la question:

absolute="/foo/bar"
current="/foo/baz/foo"
relative="$(relpathurl "$absolute" "$current/")"
echo "$relative"

si vous trouvez quelque chose qui ne fonctionne pas, merci de me le faire savoir dans les commentaires ci-dessous. Grâce.

PS:

Pourquoi les arguments de relpath "inversé", contrairement à toutes les autres réponses ici?

si vous changez

Y="$(readlink -m -- "")" || return

à

Y="$(readlink -m -- "${2:-"$PWD"}")" || return

alors vous pouvez laisser le 2e paramètre loin, de sorte que la BASE est le répertoire courant/URL/n'importe quoi. Ce n'est que le principe Unix, comme d'habitude.

si vous n'aimez pas ça, retournez à Windows. Grâce.

-1
répondu Tino 2018-08-30 14:19:30