Exemplo:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
Como crio a mágica (espero que não seja um código muito complicado ...)?
bash
shell
path
relative-path
absolute-path
Paul Tarjan
fonte
fonte
realpath --relative-to=$absolute $current
.Respostas:
Usar o caminho real do GNU coreutils 8.23 é o mais simples, eu acho:
Por exemplo:
fonte
$ realpath --relative-to="${PWD}" "$file"
é útil se você deseja os caminhos relativos ao diretório de trabalho atual./usr/bin/nmap/
caminho-mas não para/usr/bin/nmap
: denmap
para/tmp/testing
ele é apenas../../
e não três vezes../
. Funciona no entanto, porque fazer..
no rootfs é/
.--relative-to=…
espera um diretório e NÃO verifica. Isso significa que você acaba com um "../" extra se você solicitar um caminho relativo a um arquivo (como este exemplo parece fazer, porque/usr/bin
raramente ou nunca contém diretórios enmap
normalmente é um binário)dá:
fonte
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:]))'
funciona de maneira mais natural e confiável.Esse é um aprimoramento corrigido e totalmente funcional da solução atualmente melhor classificada do @pini (que infelizmente lida com apenas alguns casos)
Lembrete: teste '-z' se a sequência tiver comprimento zero (= vazio) e teste '-n' se a sequência não estiver vazia.
Casos de teste :
fonte
source=$1; target=$2
comsource=$(realpath $1); target=$(realpath $2)
realpath
é recomendado, ousource=$(readlink -f $1)
etc., se realpath não está disponível (não standard)$source
e$target
assim: `if [[-e $ 1]]; então source = $ (readlink -f $ 1); outra fonte = $ 1; fi se [[-e $ 2]]; então target = $ (readlink -f $ 2); else target = $ 2; Dessa maneira, a função poderia agrupar caminhos relativos reais / existentes, além de diretórios fictícios.readlink
tem uma-m
opção que só faz isso;)fonte
Ele é incorporado ao Perl desde 2001, portanto funciona em quase todos os sistemas que você pode imaginar, até no VMS .
Além disso, a solução é fácil de entender.
Então, para o seu exemplo:
... funcionaria bem.
fonte
say
não estava disponível em perl como log, mas poderia ser usado efetivamente aqui.perl -MFile::Spec -E 'say File::Spec->abs2rel(@ARGV)'
perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target"
eperl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$target" "$origin"
. O primeiro script perl de uma linha usa um argumento (origem é o diretório de trabalho atual). O segundo script perl de uma linha usa dois argumentos.perl
pode ser encontrado em quase todos os lugares, embora a resposta ainda seja uma linha.Presumindo que você tenha instalado: bash, pwd, dirname, echo; então relpath é
Eu joguei a resposta de pini e algumas outras idéias
Nota : Isso requer que os dois caminhos sejam pastas existentes. Arquivos não funcionarão.
fonte
Python
os.path.relpath
como uma função shellO objetivo deste
relpath
exercício é imitar aos.path.relpath
função do Python 2.7 (disponível no Python versão 2.6, mas funcionando apenas corretamente no 2.7), conforme proposto por xni . Como conseqüência, alguns dos resultados podem diferir das funções fornecidas em outras respostas.(Eu não testei com novas linhas nos caminhos simplesmente porque interrompe a validação com base em chamadas
python -c
do ZSH. Certamente seria possível com algum esforço.)Em relação à “mágica” no Bash, eu desisti de procurar magia no Bash há muito tempo, mas desde então encontrei toda a magia que preciso e, em seguida, algumas no ZSH.
Consequentemente, proponho duas implementações.
A primeira implementação visa ser totalmente compatível com POSIX . Eu testei com o
/bin/dash
Debian 6.0.6 "Squeeze". Também funciona perfeitamente/bin/sh
no OS X 10.8.3, que na verdade é o Bash versão 3.2, fingindo ser um shell POSIX.A segunda implementação é uma função de shell ZSH que é robusta contra múltiplas barras e outros incômodos nos caminhos. Se você tiver o ZSH disponível, esta é a versão recomendada, mesmo se você a estiver chamando no formulário de script apresentado abaixo (ou seja, com um shebang de
#!/usr/bin/env zsh
) de outro shell.Por fim, escrevi um script ZSH que verifica a saída do
relpath
comando encontrado em$PATH
determinados casos de teste fornecidos em outras respostas. Adicionei um pouco de tempero a esses testes adicionando alguns espaços, tabulações e pontuação, como! ? *
aqui e ali, e também realizei outro teste com caracteres UTF-8 exóticos encontrados no vim-powerline .Função shell POSIX
Primeiro, a função de shell compatível com POSIX. Ele funciona com vários caminhos, mas não limpa várias barras nem resolve links simbólicos.
Função shell ZSH
Agora, a
zsh
versão mais robusta . Se você deseja resolver os argumentos para os caminhos reais à larealpath -f
(disponíveis nocoreutils
pacote Linux ), substitua as:a
linhas 3 e 4 por:A
.Para usar isso no zsh, remova a primeira e a última linha e coloque-a em um diretório que esteja na sua
$FPATH
variável.Script de teste
Finalmente, o script de teste. Ele aceita uma opção, a saber,
-v
ativar a saída detalhada.fonte
/
receio.O script shell acima foi inspirado em pini's (obrigado!). Ele dispara um bug no módulo de realce da sintaxe do Stack Overflow (pelo menos no meu quadro de visualização). Portanto, ignore se o realce está incorreto.
Algumas notas:
Exceto pelas seqüências de barra invertida mencionadas, a última linha da função "relPath" gera nomes de caminho compatíveis com python:
A última linha pode ser substituída (e simplificada) por linha
Eu prefiro o último porque
Os nomes de arquivos podem ser anexados diretamente aos caminhos de diretório obtidos pelo relPath, por exemplo:
Links simbólicos no mesmo diretório criado com este método não têm o feio
"./"
anexado ao nome do arquivo.Listagem de código para testes de regressão (basta anexá-lo ao script de shell):
fonte
Poucas respostas aqui são práticas para o uso diário. Como é muito difícil fazer isso corretamente no bash puro, sugiro a seguinte solução confiável (semelhante a uma sugestão oculta em um comentário):
Em seguida, você pode obter o caminho relativo com base no diretório atual:
ou você pode especificar que o caminho seja relativo a um determinado diretório:
A única desvantagem é que isso requer python, mas:
Observe que as soluções que incluem
basename
oudirname
podem não ser necessariamente melhores, pois exigem acoreutils
instalação. Se alguém tiver umabash
solução pura que seja confiável e simples (em vez de uma curiosidade complicada), eu ficaria surpreso.fonte
Este script fornece resultados corretos apenas para entradas que são caminhos absolutos ou caminhos relativos sem
.
ou..
:fonte
Eu apenas usaria Perl para esta tarefa não tão trivial:
fonte
perl -MFile::Spec -e "print File::Spec->abs2rel('$absolute','$current')"
para que absoluto e atual sejam citados.relative=$(perl -MFile::Spec -e 'print File::Spec->abs2rel(@ARGV)' "$absolute" "$current")
. Isso garante que os valores não possam, eles próprios, conter código perl!Uma ligeira melhoria nas respostas de kasku e Pini , que são mais agradáveis com os espaços e permitem a passagem de caminhos relativos:
fonte
test.sh:
Teste:
fonte
readlink
no$(pwd)
.Mais uma solução, pura
bash
+ GNU,readlink
para fácil utilização no seguinte contexto:Isso funciona em quase todo o Linux atual. Se
readlink -m
não funcionar ao seu lado, tentereadlink -f
. Consulte também https://gist.github.com/hilbix/1ec361d00a8178ae8ea0 para obter possíveis atualizações:Notas:
*
ou?
.ln -s
:relpath / /
dá.
e não a string vaziarelpath a a
dáa
, mesmo quea
seja um diretórioreadlink
é necessária para canonizar caminhos.readlink -m
ele, também funciona para caminhos ainda não existentes.Nos sistemas antigos, onde
readlink -m
não está disponível,readlink -f
falhará se o arquivo não existir. Portanto, você provavelmente precisará de uma solução alternativa como esta (não testada!):Isso não é realmente muito correto no caso de
$1
incluir.
ou..
para caminhos inexistentes (como em/doesnotexist/./a
), mas deve cobrir a maioria dos casos.(Substitua
readlink -m --
acima porreadlink_missing
.)Editar por causa do voto negativo a seguir
Aqui está um teste, de que essa função, de fato, está correta:
Intrigado? Bem, estes são os resultados corretos ! Mesmo se você acha que isso não se encaixa na pergunta, aqui está a prova de que isso está correto:
Sem dúvida,
../bar
é o caminho relativo exato e único correto da páginabar
visto a partir da páginamoo
. Tudo o resto estaria completamente errado.É trivial adotar a saída para a pergunta que aparentemente assume, que
current
é um diretório:Isso retorna exatamente o que foi solicitado.
E antes que você levante uma sobrancelha, aqui está uma variante um pouco mais complexa de
relpath
(identifique a pequena diferença), que também deve funcionar para a sintaxe de URL (para que uma trilha/
sobreviva, graças a algunsbash
-magic):E aqui estão as verificações apenas para deixar claro: ele realmente funciona como dito.
E aqui está como isso pode ser usado para fornecer o resultado desejado da pergunta:
Se você encontrar algo que não funciona, entre em contato nos comentários abaixo. Obrigado.
PS:
Por que os argumentos de
relpath
"invertido" contrastam com todas as outras respostas aqui?Se você mudar
para
então você pode deixar o segundo parâmetro ausente, de modo que o BASE seja o diretório / URL atual / o que for. Esse é apenas o princípio Unix, como de costume.
Se você não gostar disso, volte ao Windows. Obrigado.
fonte
Infelizmente, a resposta de Mark Rushakoff (agora excluída - referenciou o código daqui ) não parece funcionar corretamente quando adaptada para:
O pensamento descrito no comentário pode ser refinado para fazê-lo funcionar corretamente na maioria dos casos. Estou prestes a assumir que o script usa um argumento de origem (onde você está) e um argumento de destino (onde você deseja chegar), e que ambos são nomes de caminho absolutos ou ambos são relativos. Se um for absoluto e o outro relativo, o mais fácil é prefixar o nome relativo no diretório de trabalho atual - mas o código abaixo não faz isso.
Cuidado
O código abaixo está quase funcionando corretamente, mas não está certo.
xyz/./pqr
'.xyz/../pqr
'../
' dos caminhos.O código de Dennis é melhor porque corrige 1 e 5 - mas tem os mesmos problemas 2, 3, 4. Use o código de Dennis (e faça uma votação antecipada antes disso) por causa disso.
(NB: POSIX fornece uma chamada do sistema
realpath()
que resolve nomes de caminho para que não haja links simbólicos neles. Aplicar isso aos nomes de entrada e usar o código de Dennis daria a resposta correta a cada vez. É trivial escrever o código C que wrapsrealpath()
- eu já fiz - mas não conheço um utilitário padrão que faça isso.)Por isso, considero o Perl mais fácil de usar do que o shell, embora o bash tenha um suporte decente para matrizes e provavelmente também possa fazer isso - exercício para o leitor. Portanto, dados dois nomes compatíveis, divida-os em componentes:
Portanto:
Script de teste (os colchetes contêm um espaço em branco e uma guia):
Saída do script de teste:
Esse script Perl funciona bastante bem no Unix (não leva em consideração todas as complexidades dos nomes de caminho do Windows) diante de entradas estranhas. Ele usa o módulo
Cwd
e sua funçãorealpath
para resolver o caminho real dos nomes que existem e faz uma análise textual dos caminhos que não existem. Em todos os casos, exceto um, ele produz a mesma saída que o script de Dennis. O caso desviante é:Os dois resultados são equivalentes - apenas não idênticos. (A saída é de uma versão levemente modificada do script de teste - o script Perl abaixo simplesmente imprime a resposta, em vez das entradas e a resposta como no script acima.) Agora: devo eliminar a resposta que não está funcionando? Talvez...
fonte
/home/part1/part2
para/
tem um demais../
. Caso contrário, meu script corresponde à sua saída, exceto a minha adiciona um desnecessário.
no final daquele em que o destino está.
e eu não uso um./
no início dos que descem sem subir.readlink /usr/bin/vi
dá/etc/alternatives/vi
, mas isso é outra ligação simbólica - ao passo quereadlink -f /usr/bin/vi
dá/usr/bin/vim.basic
, que é o destino final de todos os links simbólicos ...Tomei sua pergunta como um desafio para escrever isso em código shell "portátil", ou seja,
É executado em qualquer shell compatível com POSIX (zsh, bash, ksh, ash, busybox, ...). Ele ainda contém um conjunto de testes para verificar seu funcionamento. A canonização dos nomes de caminho é deixada como um exercício. :-)
fonte
Minha solução:
fonte
Aqui está a minha versão. É baseado na resposta de @Offirmo . Tornei-o compatível com o Dash e corrigi a seguinte falha do testcase:
./compute-relative.sh "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../..f/g/"
Agora:
CT_FindRelativePath "/a/b/c/de/f/g" "/a/b/c/def/g/"
->"../../../def/g/"
Veja o código:
fonte
Acho que esse também deve fazer o truque ... (vem com testes internos) :)
OK, espera-se alguma sobrecarga, mas estamos fazendo o Bourne aqui! ;)
fonte
Este script funciona apenas nos nomes dos caminhos. Ele não requer que nenhum dos arquivos exista. Se os caminhos transmitidos não forem absolutos, o comportamento é um pouco incomum, mas deve funcionar como esperado se os dois caminhos forem relativos.
Eu o testei apenas no OS X, portanto, pode não ser portátil.
fonte
Esta resposta não aborda a parte Bash da pergunta, mas como tentei usar as respostas nesta pergunta para implementar essa funcionalidade no Emacs, eu a divulgarei.
O Emacs realmente tem uma função para isso imediatamente:
fonte
relpath
função) se comporta de forma idênticafile-relative-name
nos casos de teste que você forneceu.Aqui está um script de shell que faz isso sem chamar outros programas:
fonte: http://www.ynform.org/w/Pub/Relpath
fonte
Eu precisava de algo assim, mas que resolvesse links simbólicos também. Descobri que o pwd tem um sinalizador -P para esse fim. Um fragmento do meu script é anexado. Está dentro de uma função em um script de shell, daí $ 1 e $ 2. O valor do resultado, que é o caminho relativo de START_ABS para END_ABS, está na variável UPDIRS. O script cd está em cada diretório de parâmetros para executar o pwd -P e isso também significa que os parâmetros do caminho relativo são tratados. Cheers, Jim
fonte