Obtendo links relativos entre dois caminhos

21

Digamos que eu tenha dois caminhos: <source_path>e <target_path>. Gostaria que o meu shell (zsh) para automaticamente descobrir se há uma maneira de representar <target_path>a partir <source_path>de um caminho relativo.

Por exemplo, vamos assumir

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

O resultado seria ../../hello/world

Por que eu preciso disso:

Eu preciso criar um link simbólico <source_path>para <target_path>usar um link simbólico relativo sempre que possível, pois caso contrário, o servidor samba não mostrará o arquivo corretamente quando eu acessar esses arquivos na rede a partir do Windows (eu não sou o administrador do sistema e não ' não tem controle sobre essa configuração)

Supondo que <target_path>e <source_path>sejam caminhos absolutos, o seguinte cria um link simbólico apontando para um caminho absoluto .

ln -s <target_path> <source_path>

para que não funcione para minhas necessidades. Eu preciso fazer isso para centenas de arquivos, então não posso corrigi-lo manualmente.

Algum shell embutido que cuida disso?

Amelio Vazquez-Reina
fonte
Você pode usar symlinkspara converter links absolutos em relativos.
Stéphane Chazelas
@StephaneChazelas how? Você poderia postar uma resposta explicando?
terdon

Respostas:

10

Você pode usar o symlinkscomando para converter caminhos absolutos em relativos:

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

Temos links absolutos, vamos convertê-los para relativo:

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

Referências

Stéphane Chazelas
fonte
Obrigado, Stephane. Isso resolve o problema raiz, então eu aceitei. Dito isto, seria ótimo ter algo (por exemplo, uma função zsh) que pega dois caminhos (origem e destino) e gera o caminho relativo da origem para o destino (sem links simbólicos aqui). Isso resolveria a pergunta feita no título também.
Amelio Vazquez-Reina
@ user815423426, terdon respondeu a essa parte #
Stéphane Chazelas
24

Tente usar o realpathcomando (parte do GNU coreutils; > = 8.23 ), por exemplo:

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

Se você estiver usando o macOS, instale a versão GNU via: brew install coreutilse use grealpath.

Observe que os dois caminhos precisam existir para que o comando seja bem-sucedido. Se você precisar do caminho relativo de qualquer maneira, mesmo que um deles não exista, adicione a opção -m.

Para obter mais exemplos, consulte Converter caminho absoluto em caminho relativo, dado um diretório atual .

kenorb
fonte
Eu recebo realpath: unrecognized option '--relative-to=.':( realpath versão 1.19
phuclv 08/02
@ LưuVĩnhPhúc Você precisa usar a versão GNU / Linux do realpath. Se você estiver em MacOS, instale via: brew install coreutils.
Kenorb
não, eu estou no Ubuntu 14.04 LTS
phuclv 08/02
@ LưuVĩnhPhúc Estou realpath (GNU coreutils) 8.26onde o parâmetro está presente. Veja: gnu.org/software/coreutils/realpath Verifique duas vezes se você não está usando um alias (por exemplo, execute como \realpath) e como instalar a versão a partir do coreutils.
Kenorb
7

Eu acho que essa solução python (tirada do mesmo post que a resposta de @ EvanTeitelman) também merece ser mencionada. Adicione isso ao seu ~/.zshrc:

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

Você pode fazer, por exemplo:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
terdon
fonte
@StephaneChazelas, foi uma pasta de cópia direta da resposta vinculada. Eu sou um cara Perl e não conheço essencialmente nenhum python, então não sei como usá-lo sys.argv. Se o código postado estiver errado ou puder ser aprimorado, sinta-se à vontade para fazê-lo com meus agradecimentos.
terdon
@StephaneChazelas Agora eu entendo, obrigado pela edição.
terdon
7

Não há nenhum shell embutido para cuidar disso. Eu encontrei uma solução no Stack Overflow . Copiei a solução aqui com pequenas modificações e marquei esta resposta como wiki da comunidade.

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

Você pode usar esta função da seguinte maneira:

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

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

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

Esta é a solução mais simples e mais bonita para o problema.

Também deve funcionar em todas as conchas semelhantes a bourne.

E a sabedoria aqui é: não há absolutamente nenhuma necessidade de serviços externos ou mesmo condições complicadas. Algumas substituições de prefixo / sufixo e um loop while são tudo o que você precisa para fazer qualquer coisa em shells semelhantes a bourne.

Também disponível via PasteBin: http://pastebin.com/CtTfvime

Arto Pekkanen
fonte
Obrigado pela sua solução. Descobri que, para fazê-lo funcionar, Makefileprecisava adicionar um par de aspas duplas à linha pos=${pos%/*}(e, é claro, ponto-e-vírgula e barras invertidas no final de cada linha).
circonflexe
Idéia não é ruim, mas, na versão atual abundância de casos não funcionam: relpath /tmp /tmp, relpath /tmp /tmp/a, relpath /tmp/a /. Em adição, você se esquece de mencionar que todos os caminhos devem ser absoluta e canonicalized (ou seja, /tmp/a/..não trabalho)
Jérôme Pouiller
0

Eu tive que escrever um script bash para fazer isso de forma confiável (e, claro, sem verificar a existência do arquivo).

Uso

relpath <symlink path> <source path>

Retorna um caminho de origem relativo.

exemplo:

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

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

ambos recebem ../../CC/DD/EE


Para fazer um teste rápido, você pode executar

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

resultado: uma lista de teste

/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, se você deseja usar esse script sem salvá-lo e confia nesse script, há duas maneiras de fazer isso com o truque do bash.

Importe relpathcomo uma função bash seguindo o comando (Requer bash 4+)

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

ou chame o script on-the-fly como com curl bash combo.

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
fonte