Como recuperar o caminho absoluto dado relativo


159

Existe um comando para recuperar o caminho absoluto, dado o caminho relativo?

Por exemplo, quero que $ line contenha o caminho absoluto de cada arquivo em dir ./etc/

find ./ -type f | while read line; do
   echo $line
done


1
não duplicado stright, mas semelhante
mpapis


Uma solução muito melhor do que qualquer um dos listados até agora é aqui how-to-convertido-relative-path-to-absolute-path-in-unix
Daniel Genin

Respostas:


66

usar:

find "$(pwd)"/ -type f

para obter todos os arquivos ou

echo "$(pwd)/$line"

para exibir o caminho completo (se o caminho relativo for importante)


2
e se eu especificar um caminho relativo na localização?
Nubme

17
em seguida, ver links no comentário à sua pergunta, melhor maneira provavelmente é 'readlink -f $ line'
mpapis

3
Por que usar $(pwd)no lugar de $PWD? (sim, eu sei que pwdé um builtin)
Adam Katz

178

Tente realpath.

~ $ sudo apt-get install realpath  # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc

Para evitar a expansão de links simbólicos, use realpath -s.

A resposta vem do " comando bash / fish para imprimir o caminho absoluto para um arquivo ".


11
realpathparece não estar disponível no Mac (OS X 10.11 "El Capitan"). :-(
Laringe Decidua

realpathtambém não parece estar disponível no CentOS 6
user5359531

6
no osx, brew install coreutilstrarárealpath
Kevin Chen

No meu Ubuntu 18.04, realpathjá está presente. Não precisei instalá-lo separadamente.
Acumenus 21/01/19

Surpreendentemente, realpathestá disponível no Git for Windows (pelo menos para mim).
Andrew Keeton

104

Se você tem o pacote coreutils instalado, geralmente pode usá-lo readlink -f relative_file_namepara recuperar o absoluto (com todos os links simbólicos resolvidos)


3
O comportamento para isso é um pouco diferente do que o usuário pede, ele também segue e resolve links simbólicos recursivos em qualquer lugar do caminho. Você pode não querer isso em alguns casos.
precisa saber é o seguinte

1
@BradPeabody Isso funciona em um Mac se você instalar o coreutils a partir do homebrew brew install coreutils. No entanto, o executável é prefixado por ag:greadlink -f relative_file_name
Miguel Isla

2
Observe que a página de manual do readlink (1) tem como primeira frase sua descrição: "Nota realpath (1) é o comando preferido para usar na funcionalidade de canonização".
Josch

1
você pode usar -e em vez de -f para verificar se o arquivo / diretório existe ou não
Iceman

69
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

UPD Algumas explicações

  1. Este script obtém o caminho relativo como argumento "$1"
  2. Em seguida, obtemos parte dirname desse caminho (você pode passar dir ou arquivo para este script):dirname "$1"
  3. Então entramos cd "$(dirname "$1")nesse diretório relativo e obtemos o caminho absoluto para ele executando o pwdcomando shell
  4. Depois disso, anexamos o nome da base ao caminho absoluto:$(basename "$1")
  5. Como etapa final, echoele

8
Esta resposta utiliza a melhor expressão Bash
Rondo

2
readlink é a solução simples para linux, mas esta solução funciona em OSX também, então +1
thetoolman

1
Este script não é equivalente ao que realpathou readlink -ffazer. Por exemplo, ele não funciona em caminhos nos quais o último componente é um link simbólico.
Josch

2
@josch: A questão não é sobre a resolução de links simbólicos. Mas se você quiser fazer isso você pode fornecer -Popção de pwdcomando:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Eugen Konkov

4
Eu gosto da resposta, mas só funciona se o usuário puder entrar no diretório. Isso nem sempre é possível.
Matthias B

34

Pelo que vale, votei na resposta que foi escolhida, mas queria compartilhar uma solução. A desvantagem é que é apenas o Linux - passei cerca de 5 minutos tentando encontrar o equivalente OSX antes de chegar ao Stack overflow. Tenho certeza de que está lá fora.

No Linux, você pode usar readlink -eem conjunto com dirname.

$(dirname $(readlink -e ../../../../etc/passwd))

rendimentos

/etc/

E então você usa dirnamea irmã de, basenamepara obter o nome do arquivo

$(basename ../../../../../passwd)

rendimentos

passwd

Coloque tudo junto ..

F=../../../../../etc/passwd
echo "$(dirname $(readlink -e $F))/$(basename $F)"

rendimentos

/etc/passwd

Você está seguro se estiver direcionando um diretório, basenamenão retornará nada e você acabará com duas barras na saída final.


Excelente entrada com dirname, readlinke basename. Isso me ajudou a obter o caminho absoluto de um link simbólico - não o seu alvo.
kevinarpe

Não funciona quando você deseja retornar o caminho para links simbólicos (o que eu preciso fazer ...).
Tomáš Zato - Reinstate Monica

Como você pode encontrar o caminho absoluto para um caminho que não existe?
Sintetizadorpatel

@synthesizerpatel Com bastante facilidade, eu teria pensado; se eu estiver /home/GKFXdigitando touch newfile, antes de pressionar enter, você poderá descobrir que eu quero dizer "create / home / GKFX / newfile", que é um caminho absoluto para um arquivo que ainda não existe.
GKFX 24/09

25

Eu acho que este é o mais portátil:

abspath() {                                               
    cd "$(dirname "$1")"
    printf "%s/%s\n" "$(pwd)" "$(basename "$1")"
    cd "$OLDPWD"
}

Falhará se o caminho ainda não existir.


3
Não há necessidade de gravar novamente. Consulte stackoverflow.com/a/21188136/1504556 . A sua é a melhor resposta nesta página, IMHO. Para os interessados, o link fornece uma explicação do motivo pelo qual essa solução funciona.
Peterh

Isso não é muito portátil, dirnameé um utilitário principal do GNU, não é comum a todos os unixen que acredito.
einpoklum

O @einpoklum dirnameé um utilitário padrão do POSIX, consulte aqui: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A

Oh meu Deus, obrigado. Eu tenho tentado corrigir a versão que usa ${1##*/}há um dia e agora que substituí o lixo por basename "$1"ele parece finalmente estar manipulando adequadamente os caminhos que terminam em /.
L3l_aze 28/08

NB, isso não faz a coisa certa com um caminho que termina em #../..
Alex Coventry

16

realpath provavelmente é melhor

Mas ...

A questão inicial era muito confusa, para começar, com um exemplo pouco relacionado à questão, conforme indicado.

A resposta selecionada, na verdade, responde ao exemplo dado, e não à pergunta do título. O primeiro comando é essa resposta (é mesmo? Duvido), e poderia funcionar tão bem sem o '/'. E não vejo o que o segundo comando está fazendo.

Vários problemas são mistos:

  • transformando um nome de caminho relativo em um absoluto, o que quer que ele denuncie, possivelmente nada. ( Normalmente, se você emitir um comando como touch foo/bar, o nome do caminho foo/bardeve existir para você e, possivelmente, ser usado na computação, antes que o arquivo seja realmente criado. )

  • pode haver vários nomes de caminho absolutos que denotam o mesmo arquivo (ou arquivo potencial), principalmente por causa de links simbólicos (links simbólicos) no caminho, mas possivelmente por outros motivos (um dispositivo pode ser montado duas vezes como somente leitura). Pode-se ou não querer resolver explicitamente esses links simbólicos.

  • chegando ao final de uma cadeia de links simbólicos para um arquivo ou nome sem vínculo simbólico. Isso pode ou não gerar um nome de caminho absoluto, dependendo de como é feito. E pode-se, ou não, querer resolvê-lo em um nome de caminho absoluto.

O comando readlink foosem opção fornece uma resposta apenas se o argumento foofor um link simbólico, e essa resposta for o valor desse link simbólico. Nenhum outro link é seguido. A resposta pode ser um caminho relativo: qualquer que seja o valor do argumento do link simbólico.

Contudo, readlink possui opções (-f -e ou -m) que funcionarão para todos os arquivos e fornecerão um nome de caminho absoluto (aquele sem links simbólicos) para o arquivo realmente indicado pelo argumento.

Isso funciona bem para qualquer coisa que não seja um link simbólico, embora se queira usar um nome de caminho absoluto sem resolver os links simbólicos intermediários no caminho. Isso é feito pelo comandorealpath -s foo

No caso de um argumento de link simbólico, readlinkcom suas opções novamente resolverá todos os links simbólicos no caminho absoluto para o argumento, mas isso também incluirá todos os links simbólicos que podem ser encontrados seguindo o valor do argumento. Você pode não querer isso se desejar um caminho absoluto para o próprio link simbólico do argumento, e não para o que quer que ele possa vincular. Novamente, se foofor um link simbólico, realpath -s fooobterá um caminho absoluto sem resolver links simbólicos, incluindo o fornecido como argumento.

Sem a -sopção, realpathfaz praticamente o mesmo que readlink, exceto pela simples leitura do valor de um link, além de várias outras coisas. Simplesmente não está claro para mim por que readlinktem suas opções, criando aparentemente uma redundância indesejável com realpath .

Explorar a web não diz muito mais, exceto que pode haver algumas variações entre os sistemas.

Conclusão: realpathé o melhor comando para usar, com a maior flexibilidade, pelo menos para o uso solicitado aqui.


10

Minha solução favorita foi a única do @EugenKonkov, porque não implicava a presença de outros utilitários (o pacote coreutils).

Mas falhou nos caminhos relativos "." e "..", então aqui está uma versão ligeiramente aprimorada que trata desses casos especiais.

Ele ainda falha se o usuário não tiver permissão para cdentrar no diretório pai do caminho relativo.

#! /bin/sh

# Takes a path argument and returns it as an absolute path. 
# No-op if the path is already absolute.
function to-abs-path {
    local target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

7

A resposta de Eugen não funcionou muito bem para mim, mas isso fez:

    absolute="$(cd $(dirname \"$file\"); pwd)/$(basename \"$file\")"

Nota lateral, seu diretório de trabalho atual não é afetado.


Aviso: isso é script. onde $ 1 é o primeiro argumento para isso
Eugen Konkov

5

A melhor solução, imho, é a postada aqui: https://stackoverflow.com/a/3373298/9724628 .

Requer python para funcionar, mas parece cobrir todos ou quase todos os casos extremos e é uma solução muito portátil.

  1. Com a resolução de links simbólicos:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
  1. ou sem ele:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

3

No caso de find, provavelmente é mais fácil fornecer apenas o caminho absoluto para a pesquisa, por exemplo:

find /etc
find `pwd`/subdir_of_current_dir/ -type f

3

Se você estiver usando bash no Mac OS X, que não tem realpath existia nem a sua readlink pode imprimir o caminho absoluto, você pode ter escolha a não ser código sua própria versão para imprimi-lo. Aqui está a minha implementação:

(festança pura)

abspath(){
  local thePath
  if [[ ! "$1" =~ ^/ ]];then
    thePath="$PWD/$1"
  else
    thePath="$1"
  fi
  echo "$thePath"|(
  IFS=/
  read -a parr
  declare -a outp
  for i in "${parr[@]}";do
    case "$i" in
    ''|.) continue ;;
    ..)
      len=${#outp[@]}
      if ((len==0));then
        continue
      else
        unset outp[$((len-1))] 
      fi
      ;;
    *)
      len=${#outp[@]}
      outp[$len]="$i"
      ;;
    esac
  done
  echo /"${outp[*]}"
)
}

(use gawk)

abspath_gawk() {
    if [[ -n "$1" ]];then
        echo $1|gawk '{
            if(substr($0,1,1) != "/"){
                path = ENVIRON["PWD"]"/"$0
            } else path = $0
            split(path, a, "/")
            n = asorti(a, b,"@ind_num_asc")
            for(i in a){
                if(a[i]=="" || a[i]=="."){
                    delete a[i]
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            m = 0
            while(m!=n){
                m = n
                for(i=1;i<=n;i++){
                    if(a[b[i]]==".."){
                        if(b[i-1] in a){
                            delete a[b[i-1]]
                            delete a[b[i]]
                            n = asorti(a, b, "@ind_num_asc")
                            break
                        } else exit 1
                    }
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            if(n==0){
                printf "/"
            } else {
                for(i=1;i<=n;i++){
                    printf "/"a[b[i]]
                }
            }
        }'
    fi
}

(puro bsd awk)

#!/usr/bin/env awk -f
function abspath(path,    i,j,n,a,b,back,out){
  if(substr(path,1,1) != "/"){
    path = ENVIRON["PWD"]"/"path
  }
  split(path, a, "/")
  n = length(a)
  for(i=1;i<=n;i++){
    if(a[i]==""||a[i]=="."){
      continue
    }
    a[++j]=a[i]
  }
  for(i=j+1;i<=n;i++){
    delete a[i]
  }
  j=0
  for(i=length(a);i>=1;i--){
    if(back==0){
      if(a[i]==".."){
        back++
        continue
      } else {
        b[++j]=a[i]
      }
    } else {
      if(a[i]==".."){
        back++
        continue
      } else {
        back--
        continue
      }
    }
  }
  if(length(b)==0){
    return "/"
  } else {
    for(i=length(b);i>=1;i--){
      out=out"/"b[i]
    }
    return out
  }
}

BEGIN{
  if(ARGC>1){
    for(k=1;k<ARGC;k++){
      print abspath(ARGV[k])
    }
    exit
  }
}
{
  print abspath($0)
}

exemplo:

$ abspath I/am/.//..//the/./god/../of///.././war
/Users/leon/I/the/war

1

O que eles disseram, exceto find $PWDou (no bash), find ~+é um pouco mais conveniente.


1

Semelhante à resposta do @ernest-a, mas sem afetar $OLDPWDou definir uma nova função, você pode disparar um subshell(cd <path>; pwd)

$ pwd
/etc/apache2
$ cd ../cups 
$ cd -
/etc/apache2
$ (cd ~/..; pwd)
/Users
$ cd -
/etc/cups

1

Se o caminho relativo for um caminho de diretório, tente o meu, deve ser o melhor:

absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)

echo $absPath

Esta solução só funciona para bash, ver também stackoverflow.com/a/5193087/712014
Michael

1
echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'

3
Obrigado por este snippet de código, que pode fornecer ajuda limitada a curto prazo. Uma explicação adequada melhoraria bastante seu valor a longo prazo, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com outras perguntas semelhantes. Por favor edite sua resposta para adicionar alguma explicação, incluindo as suposições que você fez.
precisa

1

Uma melhoria na versão bastante agradável do @ernest-a:

absolute_path() {
    cd "$(dirname "$1")"
    case $(basename $1) in
        ..) echo "$(dirname $(pwd))";;
        .)  echo "$(pwd)";;
        *)  echo "$(pwd)/$(basename $1)";;
    esac
}

Isso lida corretamente com o caso em que está o último elemento do caminho ..; nesse caso, a "$(pwd)/$(basename "$1")"resposta do inernest-a será apresentada como accurate_sub_path/spurious_subdirectory/...


0

Se você deseja transformar uma variável que contém um caminho relativo em absoluto, isso funciona:

   dir=`cd "$dir"`

"cd" ecoa sem alterar o diretório de trabalho, porque executado aqui em um sub-shell.


2
Em bash-4.3-p46, isso não funciona: a shell imprime uma linha em branco quando eu corrodir=`cd ".."` && echo $dir
Michael

0

Essa é uma solução encadeada de todas as outras, por exemplo, quando realpathfalha, ou porque não está instalada ou porque sai com o código de erro; então, a próxima solução é tentada até encontrar o caminho certo.

#!/bin/bash

function getabsolutepath() {
    local target;
    local changedir;
    local basedir;
    local firstattempt;

    target="${1}";
    if [ "$target" == "." ];
    then
        printf "%s" "$(pwd)";

    elif [ "$target" == ".." ];
    then
        printf "%s" "$(dirname "$(pwd)")";

    else
        changedir="$(dirname "${target}")" && basedir="$(basename "${target}")" && firstattempt="$(cd "${changedir}" && pwd)" && printf "%s/%s" "${firstattempt}" "${basedir}" && return 0;
        firstattempt="$(readlink -f "${target}")" && printf "%s" "${firstattempt}" && return 0;
        firstattempt="$(realpath "${target}")" && printf "%s" "${firstattempt}" && return 0;

        # If everything fails... TRHOW PYTHON ON IT!!!
        local fullpath;
        local pythoninterpreter;
        local pythonexecutables;
        local pythonlocations;

        pythoninterpreter="python";
        declare -a pythonlocations=("/usr/bin" "/bin");
        declare -a pythonexecutables=("python" "python2" "python3");

        for path in "${pythonlocations[@]}";
        do
            for executable in "${pythonexecutables[@]}";
            do
                fullpath="${path}/${executable}";

                if [[ -f "${fullpath}" ]];
                then
                    # printf "Found ${fullpath}\\n";
                    pythoninterpreter="${fullpath}";
                    break;
                fi;
            done;

            if [[ "${pythoninterpreter}" != "python" ]];
            then
                # printf "Breaking... ${pythoninterpreter}\\n"
                break;
            fi;
        done;

        firstattempt="$(${pythoninterpreter} -c "import os, sys; print( os.path.abspath( sys.argv[1] ) );" "${target}")" && printf "%s" "${firstattempt}" && return 0;
        # printf "Error: Could not determine the absolute path!\\n";
        return 1;
    fi
}

printf "\\nResults:\\n%s\\nExit: %s\\n" "$(getabsolutepath "./asdfasdf/ asdfasdf")" "${?}"

0

Você pode usar a substituição do string bash para qualquer caminho relativo $ line:

line=$(echo ${line/#..\//`cd ..; pwd`\/})
line=$(echo ${line/#.\//`pwd`\/})
echo $line

A substituição básica da frente de cadeia segue a fórmula
$ {string / # substring / Replacement}
que é discutida aqui: https://www.tldp.org/LDP/abs/html/string-manipulation.html

O \caractere nega /quando queremos que ele faça parte da string que encontramos / substituímos.


0

Não consegui encontrar uma solução que fosse perfeitamente portátil entre o Mac OS Catalina, o Ubuntu 16 e o ​​Centos 7, por isso decidi fazê-lo com python inline e funcionou bem para meus scripts bash.

to_abs_path() {
  python -c "import os; print os.path.abspath('$1')"
}

to_abs_path "/some_path/../secrets"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.