Ottenere collegamenti relativi tra due percorsi


21

Di 'che ho due percorsi: <source_path>e <target_path>. Vorrei che il mio guscio (zsh) di trovare automaticamente se c'è un modo per rappresentare <target_path>da <source_path>un percorso relativo.

Ad esempio, supponiamo

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

Il risultato sarebbe ../../hello/world

Perché ho bisogno di questo:

Ho bisogno piace creare un link simbolico da <source_path>a <target_path>utilizzare un relativo collegamento simbolico quando possibile, dal momento che altrimenti il nostro server Samba non mostra il file in modo corretto quando ho accedere a questi file in rete da Windows (io non sono l'amministratore di sistema, e don' ho il controllo su questa impostazione)

Supponendo che <target_path>e <source_path>siano percorsi assoluti, quanto segue crea un collegamento simbolico che punta a un percorso assoluto .

ln -s <target_path> <source_path>

quindi non funziona per le mie esigenze. Devo farlo per centinaia di file, quindi non posso semplicemente ripararlo manualmente.

Qualche built-in di shell che se ne occupi?


È possibile utilizzare symlinksper convertire collegamenti assoluti in relativi.
Stéphane Chazelas,

@StephaneChazelas come? Potresti pubblicare una risposta che spiega?
terdon

Risposte:


10

È possibile utilizzare il symlinkscomando per convertire percorsi assoluti in relativi:

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

Abbiamo collegamenti assoluti, convertiamoli in relativi:

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

Riferimenti


Grazie Stephane. Questo risolve il problema alla radice, quindi ho accettato. Detto questo, sarebbe bello avere qualcosa (ad esempio una funzione zsh) che prende due percorsi (sorgente e destinazione) e genera il percorso relativo dalla fonte alla destinazione (qui non ci sono collegamenti simbolici). Ciò risolverebbe anche la domanda posta nel titolo.
Amelio Vazquez-Reina,


24

Prova a usare il realpathcomando (parte di GNU coreutils; > = 8.23 ), ad esempio:

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

Se stai usando macOS, installa la versione GNU tramite: brew install coreutilse usa grealpath.

Nota che entrambi i percorsi devono esistere affinché il comando abbia successo. Se è necessario comunque il percorso relativo anche se uno di essi non esiste, aggiungere l'opzione -m.

Per altri esempi, vedi Converti percorso assoluto in percorso relativo data una directory corrente .


Ottengo realpath: unrecognized option '--relative-to=.':( realpath versione 1.19
phuclv

@ LưuVĩnhPhúc Devi usare la versione GNU / Linux di realpath. Se siete su MacOS, installare tramite: brew install coreutils.
Kenorb,

no, sono su Ubuntu 14.04 LTS
phuclv

@ LưuVĩnhPhúc Sono su realpath (GNU coreutils) 8.26dove è presente il parametro. Vedi: gnu.org/software/coreutils/realpath Ricontrolla che non stai usando un alias (es. Run as \realpath) e come puoi installare la versione da coreutils.
Kenorb,


7

Penso che valga la pena menzionare anche questa soluzione Python (tratta dallo stesso post della risposta di @ EvanTeitelman). Aggiungi questo al tuo ~/.zshrc:

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

È quindi possibile fare, ad esempio:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs

@StephaneChazelas questa era una copia diretta della risposta collegata. Sono un ragazzo Perl e praticamente non conosco pitone, quindi non so come usarlo sys.argv. Se il codice pubblicato è errato o può essere migliorato, sentiti libero di farlo con i miei ringraziamenti.
terdon

@StephaneChazelas Ora capisco, grazie per la modifica.
terdon

7

Non ci sono builtin shell per prendersi cura di questo. Ho trovato una soluzione su Stack Overflow però. Ho copiato la soluzione qui con lievi modifiche e contrassegnato questa risposta come wiki della comunità.

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

Puoi usare questa funzione in questo modo:

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

Questa è la soluzione più semplice e più bella al problema.

Inoltre dovrebbe funzionare su tutte le conchiglie simili a bourne.

E la saggezza qui è: non c'è assolutamente bisogno di utilità esterne o condizioni complicate. Un paio di sostituzioni di prefissi / suffissi e un ciclo while sono tutto ciò che serve per fare qualsiasi cosa su shell tipo bourne.

Disponibile anche tramite PasteBin: http://pastebin.com/CtTfvime


Grazie per la tua soluzione Ho scoperto che per farlo funzionare in un Makefileho bisogno di aggiungere una coppia di virgolette doppie alla riga pos=${pos%/*}(e, naturalmente, punti e virgola e barre rovesciate alla fine di ogni riga).
Circonflexe,

Idea non è male, ma, nella versione corrente sacco di casi non funzionano: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Inoltre, ti dimentichi di menzionare che tutti i percorsi devono essere assoluti E canonizzati (cioè /tmp/a/..non funzionano)
Jérôme Pouiller

0

Ho dovuto scrivere uno script bash per farlo in modo affidabile (e ovviamente senza verificare l'esistenza del file).

uso

relpath <symlink path> <source path>

Restituisce un percorso di origine relativo.

esempio:

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

entrambi ottengono ../../CC/DD/EE


Per avere un test rapido puoi eseguire

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

risultato: un elenco di 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

A proposito, se vuoi usare questo script senza salvarlo e ti fidi di questo script, allora hai due modi per farlo con bash trick.

Importa relpathcome funzione bash seguendo il comando. (Richiede bash 4+)

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

o chiama lo script al volo come 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.