Pregunta

Dada una ruta absoluta o relativa (en un sistema de tipo Unix), me gustaría para determinar la ruta de acceso completa de la meta después de resolver cualquier intermedio de enlaces simbólicos.Puntos de bonificación para la resolución de ~nombre de usuario notación al mismo tiempo.

Si el destino es un directorio, puede ser posible chdir() en el directorio y, a continuación, llamar a getcwd(), pero lo que realmente quiero hacer esto desde un script de shell en lugar de escribir una C ayudante.Por desgracia, las conchas tienen una tendencia a tratar de ocultar la existencia de enlaces simbólicos desde el usuario (esto es bash en OS X):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

Lo que yo quiero es una función de resolver() de tal forma que cuando se ejecuta desde el directorio tmp en el ejemplo anterior, resolver("foo") == "/Usuarios/greg/tmp/bar".

¿Fue útil?

Solución

De acuerdo a las normas, pwd -P debe devolver la ruta de acceso con enlaces simbólicos resuelto.

Función de C char *getcwd(char *buf, size_t size) de unistd.h debe tener el mismo comportamiento.

getcwd pwd

Otros consejos

readlink -f "$path"

Nota del Editor:Lo anterior funciona con GNU readlink y FreeBSD/PC-BSD/OpenBSD readlink, pero no en OS X como de 10.11.
GNU readlink ofrece otras opciones relacionadas, tales como -m para resolver un enlace simbólico de si o no el objetivo último, existe.

Nota: desde GNU coreutils 8.15 (2012-01-06), existe un realpath programa disponible que es menos obtuso y más flexible que el anterior.También es compatible con el FreeBSD util del mismo nombre.También incluye la funcionalidad para generar una ruta de acceso relativa entre los dos archivos.

realpath $path

[Admin adición a continuación, a partir de comentarios por halloleodanorton]

Para Mac OS X (a través de, al menos, 10.11.x), el uso de readlink sin el -f opción:

readlink $path

Nota del Editor:Esto no va a resolver enlaces simbólicos de forma recursiva y por lo tanto no informe de la ultimate de destino;por ejemplo, dado un enlace simbólico a que apunta a b, que a su vez apunta a c, este informe b (y no asegurarse de que está de salida, como un ruta de acceso absoluta).
Utilice el siguiente perl comando en OS X para llenar el vacío de la falta readlink -f funcionalidad:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

"pwd -P" parece funcionar si sólo quieres el directorio, pero si por alguna razón usted desea que el nombre del ejecutable real no creo que ayuda.Aquí está mi solución:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

Uno de mis favoritos es realpath foo

realpath - return the canonicalized absolute pathname

realpath  expands  all  symbolic  links  and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and
       stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path.  The resulting path will have no symbolic link, '/./' or
       '/../' components.
readlink -e [filepath]

parece ser exactamente lo que estás pidiendo - acepta una arbirary camino, resuelve todos los enlaces simbólicos, y devuelve el "real" ruta de acceso - y es "estándar *nix" que probablemente todos los sistemas que ya tienen

De otra manera:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

Poner algunas de las soluciones juntos, sabiendo que readlink está disponible en la mayoría de los sistemas, pero las necesidades de los diferentes argumentos, esto funciona bien para mí en OSX y Debian.No estoy seguro acerca de los sistemas BSD.Tal vez la condición debe ser [[ $OSTYPE != darwin* ]] para excluir -f desde OSX solo.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"

Nota:Yo creo que esto es un sólido, el portátil, la solución ya preparada, que es invariablemente largo por esa misma razón.

A continuación es un totalmente compatible con POSIX función de secuencia de comandos que por lo tanto es cruz-plataforma de (obras en macOS también, cuyo readlink todavía no soporta -f como de 10.12 (Sierra)) - se utiliza sólo POSIX características del lenguaje y sólo compatible con POSIX utilidad de llamadas.

Es un portátil implementación de GNU readlink -e (la versión más estricta de readlink -f).

Usted puede ejecutar el secuencia de comandos con sh o fuente de la la función en bash, ksh, y zsh:

Por ejemplo, dentro de una secuencia de comandos que puede utilizar de la siguiente manera para obtener la ejecución del script verdadero directorio de origen, con enlaces simbólicos resuelto:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink guión / definición de la función:

El código se ha adaptado con la gratitud de esta respuesta.
También he creado un bashbasado en stand-alone, versión de la utilidad de aquí, que se puede instalar con
npm install rreadlink -g, si usted tiene Node.js instalado.

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

Una tangente sobre la seguridad:

jarno, en referencia a la función de garantizar que builtin command no es la sombra de un alias o shell función con el mismo nombre, se pregunta en un comentario:

Lo que si unalias o unset y [ se establecen como alias o funciones de shell?

La motivación detrás de rreadlink asegurar que command tiene su significado original es el uso de eludir (benignos) comodidad alias y funciones utilizan a menudo a la sombra de los comandos estándar de intérpretes de comandos interactivos, tales como la redefinición de los ls para incluir opciones favoritas.

Creo que es seguro decir que, a menos que usted está tratando con una que no es de confianza, malicioso medio ambiente, de preocuparse unalias o unset - o, para el caso, while, do, ...- ser redefinido, no es una preocupación.

Hay algo que la función debe depender de tener su significado original y el comportamiento - no hay forma de evitar eso.
Que POSIX-como conchas permitir la redefinición de builtins e incluso el lenguaje de las palabras clave es inherentemente un riesgo para la seguridad (y la escritura paranoico código es difícil en general).

Para abordar sus preocupaciones específicamente:

La función se basa en unalias y unset tener su significado original.Habiendo redefinido como ellos funciones de shell de manera que altere su comportamiento sería un problema;redefinición como un alias es no necesariamente un motivo de preocupación, debido a que citando (parte de) el nombre del comando (por ejemplo, \unalias) evita alias.

Sin embargo, citando a es no una opción para la shell palabras clave (while, for, if, do, ...) y mientras que shell palabras clave toman precedencia sobre shell funciones, en bash y zsh tienen la prioridad más alta, por lo que para protegerse contra shell-palabra clave redefiniciones debe ejecutar unalias con sus nombres (aunque en no interactivo bash conchas (tales como secuencias de comandos) alias no ampliado por defecto sólo si shopt -s expand_aliases se llama explícitamente a la primera).

Para asegurarse de que unalias - como un builtin - tiene su significado original, usted debe utilizar \unset en primer lugar, lo que requiere que unset tienen su significado original:

unset es una concha builtin, para garantizar que se invoca como tal, tendría que asegurarse de que el mismo no es redefinido como un la función.Mientras que usted puede omitir un alias formulario con cita, usted no puede omitir una shell-forma de la función - catch 22.

Por lo tanto, a menos que usted puede confiar en unset tener su significado original, por lo que puedo decir, no hay una forma garantizada para defenderse de todos los maliciosos redefiniciones.

Común de secuencias de comandos de shell a menudo tienen que encontrar su directorio "home" incluso si se invoca como un enlace simbólico.La secuencia de comandos por lo que tienen que encontrar su "verdadero" de la posición a partir de solo $0.

cat `mvn`

en mi sistema imprime una secuencia de comandos que contiene el siguiente, que debe ser una buena pista de lo que usted necesita.

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..

Intente esto:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

Desde que me he topado con este muchas veces a lo largo de los años, y esta vez necesitaba un puro bash versión portátil que podría utilizar en OSX y linux, me fui por delante y escribió uno:

La versión vidas aquí:

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

pero por el bien de ASÍ que, aquí está la versión actual (creo que es bien probado..pero estoy abierto a comentarios!)

No podría ser difícil hacer que funcione normal bourne shell (sh), pero no lo intenté...me gusta $FUNCNAME demasiado.:)

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

he aquí un ejemplo clásico, gracias a la cerveza:

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

utilice esta función y devolverá el -real - ruta:

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

Para evitar la Mac de la incompatibilidad, que se me ocurrió

echo `php -r "echo realpath('foo');"`

No muy bien, pero OS cruz

He aquí cómo se puede obtener la ruta de acceso al archivo en MacOS/Unix utilizando una línea de comandos Perl:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

Del mismo modo, para obtener el directorio de un enlace simbólico del archivo:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

Es la ruta de acceso de un directorio, o puede ser un archivo?Si es un directorio, es muy sencillo:

(cd "$DIR"; pwd -P)

Sin embargo, si puede ser un archivo, entonces esto no va a funcionar:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

debido a que el enlace podría resolver en un pariente o ruta de acceso completa.

En los scripts, necesito encontrar el verdadero camino, para que yo pudiera configuración de referencia u otros scripts que se instala junto con ella, yo uso este:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

Usted puede establecer el SOURCE para cualquier ruta de acceso del archivo.Básicamente, durante el tiempo que la ruta de acceso es un enlace simbólico, se resuelve que el enlace simbólico.El truco está en la última línea del bucle.Si el resuelto enlace simbólico es absoluta, que va a utilizar como SOURCE.Sin embargo, si es relativa, se antepondrá el DIR para ella, que se resolvió en un lugar real por el simple truco que describió por primera vez.

Creo que este es el verdadero y definitivo "con el fin de resolver un enlace simbólico" si es un directorio o un no-directorio, usando Bash:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P "$link"
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

Tenga en cuenta que todos los cd y set las cosas se lleva a cabo en una subshell.

Aquí les presento lo que creo que es una cruz-plataforma (Linux y macOS, al menos) de la solución a la respuesta que se está trabajando bien para mí en la actualidad.

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

Aquí está una macOS (¿único?) solución.Posiblemente, mejor adaptada a la pregunta original.

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top