Expandir una posible ruta relativa en bash

82

Como argumentos para mi script, hay algunas rutas de archivo. Esos pueden, por supuesto, ser relativos (o contener ~). Pero para las funciones que he escrito, necesito rutas que sean absolutas, pero que no tengan sus enlaces simbólicos resueltos.

¿Hay alguna función para esto?

laar
fuente
1
Pruebe la función readlink - andy.wordpress.com/2008/05/09/bash-equivalent-for-php-realpath
arunkumar
7
readlinkresuelve enlaces simbólicos; el OP no quiere eso.
Keith Thompson
posible duplicado de Conversión de ruta relativa en ruta absoluta
tripleee
posible duplicado del comando bash / fish para imprimir la ruta absoluta a un archivo
Trevor Boyd Smith
1
echo $(cd some_directory && pwd), no resuelve el enlace simbólico, falla si algún_directorio no existe. directorio de trabajo no afectado. o asignar a una variable MY_PATH=$(cd some_directory && pwd).
qeatzy

Respuestas:

79

MY_PATH=$(readlink -f $YOUR_ARG)resolverá rutas relativas como "./"y"../"

Considere esto también ( fuente ):

#!/bin/bash
dir_resolve()
{
cd "$1" 2>/dev/null || return $?  # cd to desired directory; if fail, quell any error messages but return exit status
echo "`pwd -P`" # output full, link-resolved path
}

# sample usage
if abs_path="`dir_resolve \"$1\"`"
then
echo "$1 resolves to $abs_path"
echo pwd: `pwd` # function forks subshell, so working directory outside function is not affected
else
echo "Could not reach $1"
fi
Amir Afghani
fuente
Eso ciertamente es bueno, pero el problema es que los enlaces SIM se resuelven y eso generaría cierta confusión con los usuarios.
laar
man pwd: "-P Muestra el directorio de trabajo físico actual (todos los enlaces simbólicos resueltos)", simplemente elimine el-P
muestra el
7
Para obtener la readlink -ffuncionalidad en OS X, puede usar Homebrew y luego ejecutar: $ brew install coreutils Luego puede usar greadlink -f.
jgpawletko
45

http://www.linuxquestions.org/questions/programming-9/bash-script-return-full-path-and-filename-680368/page3.html tiene lo siguiente

function abspath {
    if [[ -d "$1" ]]
    then
        pushd "$1" >/dev/null
        pwd
        popd >/dev/null
    elif [[ -e "$1" ]]
    then
        pushd "$(dirname "$1")" >/dev/null
        echo "$(pwd)/$(basename "$1")"
        popd >/dev/null
    else
        echo "$1" does not exist! >&2
        return 127
    fi
}

que usa pushd/ popdpara entrar en un estado donde pwdes útil.

Mike Samuel
fuente
2
Lamentablemente, esta es realmente la mejor respuesta aquí y no ha sido votada a la que pertenece. +1 por responder con precisión a la pregunta del OP. Quiere que los enlaces simbólicos no se resuelvan, que en realidad es una cuestión de local de bash DIRSTACKy nada más. ¡Buena solución!
cptstubing06
5
Cuando se busca shcompatibilidad estándar , pushd; cd <dir>; <cmd>; popdse puede reemplazar con (cd <dir>; <cmd>).
Abbafei
16

De una sola línea simple:

function abs_path {
  (cd "$(dirname '$1')" &>/dev/null && printf "%s/%s" "$PWD" "${1##*/}")
}

Uso:

function do_something {
    local file=$(abs_path $1)
    printf "Absolute path to %s: %s\n" "$1" "$file"
}
do_something $HOME/path/to/some\ where

Todavía estoy tratando de descubrir cómo puedo hacer que sea completamente ajeno a si la ruta existe o no (para que también se pueda usar al crear archivos).

andsens
fuente
1
¿No cambiará esto el directorio de trabajo actual? ¿Debería cd -restablecerlo después?
devios1
4
No hay necesidad. Tenga en cuenta los parens, hacen que los comandos internos se ejecuten en una subcapa. El estado (por ejemplo, el directorio de trabajo) de un subshell no se filtra a su shell padre. Lea más aquí: tldp.org/LDP/abs/html/subshells.html
publica el
1
Bonito de una sola línea. Sin embargo, hay un problema: si el valor expandido de $ 1 no tiene ninguna barra oblicua (es decir, es un nombre de archivo en el directorio actual), $ {1% / *} se expande al nombre de archivo en sí y el comando cd falla. Es posible que desee utilizar $ (dirname $ 1) en su lugar, que se expandirá a '.' en ese caso.
Paulo
Tienes razón, actualizado. Sin embargo, tengo algunos problemas de citas al reemplazar ${1##*/}con basename, sin ponerlo en una variable separada, las rutas con espacios no funcionan correctamente.
andsens
1
@BryanP De hecho, terminé yendo por la borda por el sincronizador de archivos de puntos que estoy manteniendo. Tiene una función clean_path () que elimina las partes de la ruta innecesarias, si coloca una ruta relativa al final $PWDy la ejecuta, debería funcionar bastante bien: github.com/andsens/homeshick/blob/…
andsens
8

Esto me funciona en OS X: $(cd SOME_DIRECTORY 2> /dev/null && pwd -P)

Debería funcionar en cualquier lugar. Las otras soluciones parecían demasiado complicadas.

Mikey
fuente
4

en OS X puedes usar

stat -f "%N" YOUR_PATH

en Linux es posible que tenga realpathejecutable. de lo contrario, lo siguiente podría funcionar (no solo para los enlaces):

readlink -c YOUR_PATH
Vitaly Kushner
fuente
20
La respuesta de OS X no funciona. stat -f "%N" PATHme da exactamente el mismo camino que le di.
Lily Ballard
3

Utilice readlink -f <relative-path>, por ejemplo

export FULLPATH=`readlink -f ./`
isapir
fuente
2

Tal vez esto sea más legible y no use una subcapa y no cambie el directorio actual:

dir_resolve() {
  local dir=`dirname "$1"`
  local file=`basename "$1"`
  pushd "$dir" &>/dev/null || return $? # On error, return error code
  echo "`pwd -P`/$file" # output full, link-resolved path with filename
  popd &> /dev/null
}
sja
fuente
1

Hay otro método. Puede usar la incrustación de Python en el script bash para resolver una ruta relativa.

abs_path=$(python3 - <<END
from pathlib import Path
path = str(Path("$1").expanduser().resolve())
print(path)
END
)
cy8g3n
fuente
0

autoedición, acabo de notar que el OP dijo que no está buscando enlaces simbólicos resueltos:

"Pero para las funciones que he escrito, necesito rutas que sean absolutas, pero no resuelvan sus enlaces simbólicos".

Así que supongo que, después de todo, esto no es tan apropiado para su pregunta. :)

Como me he encontrado con esto muchas veces a lo largo de los años, y esta vez necesitaba una versión portátil pura de bash que pudiera usar en OSX y Linux, seguí adelante y escribí una:

La versión viva vive aquí:

https://github.com/keen99/shell-functions/tree/master/resolve_path

pero por el bien de SO, aquí está la versión actual (creo que está bien probada ... ¡pero estoy abierto a recibir comentarios!)

Puede que no sea difícil hacerlo funcionar para bourne shell simple (sh), pero no lo intenté ... Me gusta demasiado $ FUNCNAME. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

aquí hay un ejemplo clásico, gracias a brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

use esta función y devolverá la ruta -real-:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn
afilado
fuente
0

Si su sistema operativo lo admite, utilice:

realpath "./some/dir"

Y usándolo en una variable:

some_path="$(realpath "./some/dir")"

Que ampliará tu camino. Probado en Ubuntu y CentOS, es posible que no esté disponible en el suyo. Algunos recomiendan readlink, pero la documentación para readlink dice:

Tenga en cuenta que realpath (1) es el comando preferido para usar para la funcionalidad de canonicalización.

En caso de que la gente se pregunte por qué cito mis variables, es para preservar espacios en las rutas. Hacer realpath some pathme gusta le dará dos resultados de ruta diferentes. Pero realpath "some path"devolverá uno. Parámetros citados ftw :)

Redada
fuente
-1

¿Tienes que usar bash exclusivamente? Necesitaba hacer esto y me cansé de las diferencias entre Linux y OS X. Así que usé PHP para una solución rápida y sucia.

#!/usr/bin/php <-- or wherever
<?php
{
   if($argc!=2)
      exit();
   $fname=$argv[1];
   if(!file_exists($fname))
      exit();
   echo realpath($fname)."\n";
}
?>

Sé que no es una solución muy elegante, pero funciona.

David G
fuente
Esto no hace más que devolver la ruta al archivo si ya conoce la ruta al archivo o si el archivo está en el directorio actual ... no encuentra el archivo en la configuración de PATH
G-Man