Cómo recuperar la ruta absoluta dada relativa


160

¿Hay un comando para recuperar la ruta absoluta dada la ruta relativa?

Por ejemplo, quiero que $ line contenga la ruta absoluta de cada archivo en dir ./etc/

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


1
no es un duplicado completo, pero similar
mpapis


Una solución mucho mejor que cualquiera de las enumeradas hasta ahora es aquí cómo convertir-ruta-relativa-a-ruta-absoluta-en-unix
Daniel Genin

Respuestas:


67

utilizar:

find "$(pwd)"/ -type f

para obtener todos los archivos o

echo "$(pwd)/$line"

para mostrar la ruta completa (si la ruta relativa es importante)


2
¿Qué pasa si especifico una ruta relativa en el hallazgo?
nubme

17
luego vea los enlaces en el comentario a su pregunta, la mejor manera probablemente sea 'readlink -f $ line'
mpapis

3
¿Por qué usar $(pwd)en lugar de $PWD? (sí, sé que pwdes una construcción)
Adam Katz

178

Tratar realpath.

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

Para evitar expandir los enlaces simbólicos, use realpath -s .

La respuesta proviene del " comando bash / fish para imprimir la ruta absoluta a un archivo ".


11
realpathno parece estar disponible en Mac (OS X 10.11 "El Capitan"). :-(
Laryx Decidua

realpathtampoco parece estar disponible en CentOS 6
user5359531

66
en osx, brew install coreutilstraerárealpath
Kevin Chen

En mi Ubuntu 18.04, realpathya está presente. No tuve que instalarlo por separado.
Acumenus

Sorprendentemente, realpathestá disponible en Git para Windows (para mí, al menos).
Andrew Keeton el

104

Si tiene instalado el paquete coreutils, generalmente puede usarlo readlink -f relative_file_namepara recuperar el absoluto (con todos los enlaces simbólicos resueltos)


3
El comportamiento para esto es un poco diferente de lo que pregunta el usuario, también seguirá y resolverá enlaces simbólicos recursivos en cualquier parte de la ruta. Es posible que no desee eso en algunos casos.
incipiente

1
@BradPeabody Esto funciona en una Mac si instala coreutils desde homebrew brew install coreutils. Sin embargo, el ejecutable está precedido por ag:greadlink -f relative_file_name
Miguel Isla

2
Observe que la página del manual de readlink (1) tiene como primera oración de su descripción: "Note realpath (1) es el comando preferido para la funcionalidad de canonicalización".
josch

1
puede usar -e en lugar de -f para verificar si el archivo / directorio existe o no
Iceman

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

UPD Algunas explicaciones

  1. Este script obtiene una ruta relativa como argumento "$1"
  2. Entonces conseguimos nombredir parte de ese camino (se puede pasar a cualquiera dir o archivo a esta secuencia de comandos):dirname "$1"
  3. Luego entramos cd "$(dirname "$1")en este directorio relativo y obtenemos la ruta absoluta al ejecutarlopwd comando de shell
  4. Después de eso agregamos basename a la ruta absoluta:$(basename "$1")
  5. Como paso final que echose

8
Esta respuesta utiliza el mejor modismo Bash
Rondo

2
readlink es la solución simple para Linux, pero esta solución también funciona en OSX, por lo que +1
thetoolman

1
Este script no es equivalente a qué realpatho readlink -fhacer. Por ejemplo, no funciona en rutas donde el último componente es un enlace simbólico.
josch

2
@josch: La pregunta no es sobre la resolución de enlaces simbólicos. Pero si desea hacerlo, puede proporcionar la -Popción de pwdcomando:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Eugen Konkov

44
Me gusta la respuesta, pero solo funciona si el usuario tiene permiso para ingresar en el directorio. Esto podría no ser siempre posible.
Matthias B

34

Por lo que vale, voté por la respuesta que se eligió, pero quería compartir una solución. La desventaja es que solo es Linux: pasé unos 5 minutos tratando de encontrar el equivalente de OSX antes de llegar al desbordamiento de Stack. Sin embargo, estoy seguro de que está ahí afuera.

En Linux puedes usarlo readlink -een conjunto con dirname.

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

rendimientos

/etc/

Y luego usas dirname la hermana de, basenamepara obtener el nombre del archivo

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

rendimientos

passwd

Ponlo todo junto..

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

rendimientos

/etc/passwd

Estás seguro si estás apuntando a un directorio, basenameno devolverás nada y terminarás con dos barras diagonales en la salida final.


Excelente entrada con dirname, readlinky basename. Eso me ayudó a obtener el camino absoluto de un enlace simbólico, no su objetivo.
kevinarpe

No funciona cuando desea devolver la ruta a los enlaces simbólicos (lo que simplemente necesito hacer ...).
Tomáš Zato - Restablece a Mónica el

¿Cómo podrías encontrar el camino absoluto a un camino que no existe?
sintetizadorpatel

@synthesizerpatel Muy fácilmente, habría pensado; si estoy dentro /home/GKFXy touch newfileescribo, entonces antes de presionar enter, podría resolver que me refiero a "crear / inicio / GKFX / newfile", que es una ruta absoluta a un archivo que aún no existe.
GKFX

25

Creo que este es el más portátil:

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

Sin embargo, fallará si la ruta no existe.


3
No hay necesidad de volver a cd. Ver stackoverflow.com/a/21188136/1504556 . La suya es la mejor respuesta en esta página, en mi humilde opinión. Para aquellos interesados, el enlace ofrece una explicación de por qué funciona esta solución.
Peter

Esto no es muy portátil, dirnamees una utilidad central de GNU, no es común a todos los unixen, creo.
einpoklum

@einpoklum dirnamees una utilidad estándar POSIX, vea aquí: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A

Oh dios mío, gracias. He estado tratando de arreglar la versión que usa ${1##*/}por un día, y ahora que reemplacé esa basura basename "$1"parece que finalmente está manejando adecuadamente las rutas que terminan en /.
l3l_aze

NB, esto no hace lo correcto con un camino que termina en../..
Alex Coventry

16

realpath es probablemente el mejor

Pero ...

La pregunta inicial fue muy confusa para comenzar, con un ejemplo mal relacionado con la pregunta como se indicó.

La respuesta seleccionada en realidad responde al ejemplo dado, y en absoluto a la pregunta en el título. El primer comando es esa respuesta (¿es realmente? Dudo), y podría funcionar también sin el '/'. Y no veo qué hace el segundo comando.

Varios problemas son mixtos:

  • cambiando un nombre de ruta relativo en uno absoluto, lo que sea que denote, posiblemente nada. ( Normalmente, si emite un comando como touch foo/bar, el nombre de ruta foo/bardebe existir para usted, y posiblemente debe usarse en el cálculo, antes de que el archivo se cree realmente ) .

  • puede haber varios nombres de ruta absolutos que denotan el mismo archivo (o archivo potencial), especialmente debido a enlaces simbólicos (enlaces simbólicos) en la ruta, pero posiblemente por otras razones (un dispositivo puede montarse dos veces como de solo lectura). Uno puede o no querer resolver explícitamente dichos enlaces simbólicos.

  • llegar al final de una cadena de enlaces simbólicos a un archivo o nombre sin enlace simbólico. Esto puede o no producir un nombre de ruta absoluto, dependiendo de cómo se haga. Y uno puede o no querer resolverlo en una ruta absoluta.

El comando readlink foosin opción da una respuesta solo si su argumento fooes un enlace simbólico, y esa respuesta es el valor de ese enlace simbólico. No se sigue ningún otro enlace. La respuesta puede ser una ruta relativa: cualquiera que sea el valor del argumento del enlace simbólico.

Sin embargo, readlinktiene opciones (-f -e o -m) que funcionarán para todos los archivos y le darán un nombre de ruta absoluto (el que no tiene enlaces simbólicos) al archivo realmente denotado por el argumento.

Esto funciona bien para cualquier cosa que no sea un enlace simbólico, aunque uno podría desear usar un nombre de ruta absoluto sin resolver los enlaces simbólicos intermedios en la ruta. Esto lo hace el comandorealpath -s foo

En el caso de un argumento de enlace simbólico, readlinkcon sus opciones volverá a resolver todos los enlaces simbólicos en la ruta absoluta al argumento, pero eso también incluirá todos los enlaces simbólicos que puedan encontrarse siguiendo el valor del argumento. Es posible que no desee eso si desea una ruta absoluta al enlace simbólico del argumento en sí mismo, en lugar de a lo que sea que lo vincule. Nuevamente, si fooes un enlace simbólico, realpath -s fooobtendrá una ruta absoluta sin resolver enlaces simbólicos, incluido el que se proporciona como argumento.

Sin la -sopción, realpathhace más o menos lo mismo que readlink, excepto simplemente leer el valor de un enlace, así como varias otras cosas. Simplemente no está claro para mí por qué readlinktiene sus opciones, creando aparentemente una redundancia indeseable con realpath .

Explorar la web no dice mucho más, excepto que puede haber algunas variaciones entre los sistemas.

Conclusión: realpathes el mejor comando para usar, con la mayor flexibilidad, al menos para el uso solicitado aquí.


10

Mi solución favorita fue la de @EugenKonkov porque no implicaba la presencia de otras utilidades (el paquete coreutils).

Pero falló para las rutas relativas "." y "..", así que aquí hay una versión ligeramente mejorada que maneja estos casos especiales.

Sin embargo, todavía falla si el usuario no tiene permiso para cdingresar al directorio principal de la ruta relativa.

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

La respuesta de Eugen no funcionó para mí, pero esto sí:

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

Nota al margen, su directorio de trabajo actual no se ve afectado.


Aviso: eso es guión. donde $ 1 es el primer argumento para ello
Eugen Konkov

5

La mejor solución, en mi opinión, es la publicada aquí: https://stackoverflow.com/a/3373298/9724628 .

Requiere python para funcionar, pero parece cubrir todos o la mayoría de los casos extremos y ser una solución muy portátil.

  1. Con la resolución de enlaces simbólicos:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
  1. o sin ella:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

3

En el caso de find, probablemente sea más fácil simplemente dar la ruta absoluta para que busque, por ejemplo:

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

3

Si está utilizando bash en Mac OS X que ni ha existido realpath ni su enlace de lectura puede imprimir la ruta absoluta, puede tener la opción de codificar su propia versión para imprimirla. Aquí está mi implementación:

(puro golpe)

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[*]}"
)
}

(usa 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)
}

ejemplo:

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

1

Lo que dijeron, excepto find $PWDo (en bash) find ~+es un poco más conveniente.


1

Similar a la respuesta de @ ernest-a pero sin afectar $OLDPWDo definir una nueva función, podría disparar una subshell(cd <path>; pwd)

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

1

Si la ruta relativa es una ruta de directorio, entonces prueba la mía, debería ser la mejor:

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

echo $absPath

Esta solución solo funciona para bash, consulte también 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
Gracias por este fragmento de código, que podría proporcionar una ayuda limitada a corto plazo. Una explicación adecuada mejoraría en gran medida su valor a largo plazo al mostrar por qué esta es una buena solución al problema y lo haría más útil para futuros lectores con otras preguntas similares. Por favor, editar su respuesta a añadir un poco de explicación, incluyendo los supuestos realizados.
Toby Speight

1

Una mejora a la versión bastante agradable de @ ernest-a:

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

Esto trata correctamente con el caso donde se encuentra el último elemento de la ruta .., en cuyo caso la "$(pwd)/$(basename "$1")"respuesta de in @ ernest-a aparecerá como accurate_sub_path/spurious_subdirectory/...


0

Si desea transformar una variable que contiene una ruta relativa en una absoluta, esto funciona:

   dir=`cd "$dir"`

"cd" hace eco sin cambiar el directorio de trabajo, porque se ejecuta aquí en un sub-shell.


2
En bash-4.3-p46, esto no funciona: el shell imprime una línea vacía cuando corrodir=`cd ".."` && echo $dir
Michael

0

Esta es una solución encadenada de todas las demás, por ejemplo, cuando realpathfalla, ya sea porque no está instalada o porque sale con un código de error, entonces, la siguiente solución se intenta hasta que se obtiene el camino correcto.

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

Puede usar la sustitución de cadena bash para cualquier ruta relativa $ line:

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

La sustitución básica de la parte delantera de la cadena sigue la fórmula
$ {string / # substring / replace},
que se trata bien aquí: https://www.tldp.org/LDP/abs/html/string-manipulation.html

El \carácter niega /cuando queremos que sea parte de la cadena que encontramos / reemplazamos.


0

No pude encontrar una solución que fuera perfectamente portátil entre Mac OS Catalina, Ubuntu 16 y Centos 7, así que decidí hacerlo con Python en línea y funcionó bien para mis scripts de bash.

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

to_abs_path "/some_path/../secrets"
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.