Cómo resolver enlaces simbólicos en un script de shell

220

Dada una ruta absoluta o relativa (en un sistema similar a Unix), me gustaría determinar la ruta completa del objetivo después de resolver cualquier enlace simbólico intermedio. Puntos de bonificación por resolver también la ~ notación de nombre de usuario al mismo tiempo.

Si el objetivo es un directorio, podría ser posible chdir () en el directorio y luego llamar a getcwd (), pero realmente quiero hacer esto desde un script de shell en lugar de escribir un asistente de C. Desafortunadamente, los shells tienden a tratar de ocultar la existencia de enlaces simbólicos al 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 quiero es una función resolve () tal que cuando se ejecute desde el directorio tmp en el ejemplo anterior, resolve ("foo") == "/ Users / greg / tmp / bar".

Greg Hewgill
fuente

Respuestas:

92

De acuerdo con los estándares, pwd -Pdebe devolver la ruta con enlaces simbólicos resueltos.

Función C char *getcwd(char *buf, size_t size)desde unistd.hdebería tener el mismo comportamiento.

getcwd pwd

kauppi
fuente
22
¿Esto solo funciona para el directorio (actual)? Si el objetivo es un archivo, no dará nada ...
dala
44
No funciona si el enlace está roto, ya que no puedes convertirlo en tu camino actual
Tom Howard
66
Para resumir con el beneficio de la retrospectiva: esta respuesta funciona solo en circunstancias muy limitadas, es decir, si el enlace simbólico de interés es un directorio que realmente existe ; Además, debe cdhacerlo primero, antes de llamar pwd -P. En otras palabras: no le permitirá resolver (ver el objetivo de) enlaces simbólicos a archivos o enlaces simbólicos rotos , y para resolver enlaces simbólicos de directorios existentes debe realizar un trabajo adicional (restaurar el directorio de trabajo anterior o localizar las llamadas cdy pwd -Pen una subshell).
mklement0
mal, busco una manera de resolver un archivo y no un directorio.
Martin
Como otros han señalado, esto realmente no responde la pregunta. La respuesta de @ pixelbeat a continuación sí.
Erstaples
401
readlink -f "$path"

Nota del editor: lo anterior funciona con GNU readlink y FreeBSD / PC-BSD / OpenBSD readlink , pero no en OS X a partir de 10.11.
GNU readlink ofrece opciones adicionales relacionadas, como -mpor ejemplo para resolver un enlace simbólico si existe o no el objetivo final.

Tenga en cuenta que desde GNU coreutils 8.15 (2012-01-06), hay un programa realpath disponible que es menos obtuso y más flexible que el anterior. También es compatible con la utilidad FreeBSD del mismo nombre. También incluye funcionalidad para generar una ruta relativa entre dos archivos.

realpath $path

[ Adición de administrador a continuación del comentario de halloleo - danorton]

Para Mac OS X (hasta al menos 10.11.x), use readlinksin la -fopción:

readlink $path

Nota del editor: Esto no resolverá los enlaces simbólicos de forma recursiva y, por lo tanto, no informará el objetivo final ; por ejemplo, dado el enlace simbólico aque apunta b, que a su vez apunta c, esto solo informará b(y no garantizará que se muestre como una ruta absoluta ).
Use el siguiente perlcomando en OS X para llenar el vacío de la readlink -ffuncionalidad que falta :
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

pixelbeat
fuente
55
Esto no funciona en Mac OS X: consulte stackoverflow.com/questions/1055671/…
Bkkbrad el
1
lástima la incompatibilidad de OS X, de lo contrario agradable +1
jkp
11
En OS X puede instalar coreutils con homebrew. Lo instala como "grealpath".
Kief
10
readlinkfunciona en OSX, pero necesita otra sintaxis: readlink $path sin el -f.
halloleo
2
readlink falla de retirar la información de múltiples capas de enlace simbólico, sin embargo, que sólo derefs una capa a la vez
Magnus
26

"pwd -P" parece funcionar si solo quieres el directorio, pero si por alguna razón quieres el nombre del ejecutable real, no creo que eso ayude. 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
tlrobinson
fuente
17

Uno de mis favoritos es realpath foo

realpath: devuelve el nombre de ruta absoluto canonicalizado

realpath expande todos los enlaces simbólicos y resuelve referencias a '/./', '/../' y caracteres '/' adicionales en la cadena terminada en nulo nombrada por ruta y
       almacena el nombre de ruta absoluto canonicalizado en el búfer de tamaño PATH_MAX nombrado por camino_resuelto. La ruta resultante no tendrá un enlace simbólico, '/./' o
       Componentes '/../'.
Gregory
fuente
En Debian (etch y posterior) este comando está disponible en el paquete realpath.
Phil Ross
2
realpath ahora es (enero de 2012) parte de coreutils y es compatible con versiones anteriores de Debian y BSD
pixelbeat
1
No tengo realpathCentos 6 con GNU coreutils 8.4.31. Me he encontrado con muchos otros en Unix y Linux que tienen un núcleo de GNU empaquetado sin él realpath . Por lo tanto, parece depender de algo más que una versión.
toxalot
Prefiero realpathsobre readlinkporque ofrece banderas de opciones como--relative-to
arr_sea
10
readlink -e [filepath]

parece ser exactamente lo que está pidiendo: acepta una ruta arbitraria, resuelve todos los enlaces simbólicos y devuelve la ruta "real", y es "estándar * nix" que probablemente todos los sistemas ya tengan

Chuck Kollars
fuente
Acabo de ser mordido por eso. No funciona en Mac y estoy buscando un reemplazo.
dhill
5

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|"; } 
Keymon
fuente
Necesito cd en myreadlink (), porque es una función recursiva, ir a cada directorio hasta que encuentre un enlace. Si encuentra un enlace, devuelve la ruta real y luego sed reemplazaría la ruta.
Keymon
5

Al reunir algunas de las soluciones dadas, sabiendo que readlink está disponible en la mayoría de los sistemas, pero necesita 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* ]]excluir -fsolo de OSX.

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

Así es como se puede obtener la ruta real al archivo en MacOS / Unix usando un script Perl en línea:

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

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

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

¿Es su ruta un directorio, o podría ser un archivo? Si es un directorio, es simple:

(cd "$DIR"; pwd -P)

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

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

porque el enlace simbólico podría resolverse en una ruta relativa o completa.

En las secuencias de comandos, necesito encontrar la ruta real, para poder hacer referencia a la configuración u otras secuencias de comandos instaladas junto con ella, utilizo esto:

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

Puede configurar SOURCEcualquier ruta de archivo. Básicamente, mientras la ruta sea un enlace simbólico, resuelve ese enlace simbólico. El truco está en la última línea del bucle. Si el enlace simbólico resuelto es absoluto, lo usará como SOURCE. Sin embargo, si es relativo, antepondrá el DIRpara ello, que se resolvió en una ubicación real mediante el simple truco que describí por primera vez.

Daniel C. Sobral
fuente
2
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"))/..
Dave
fuente
Esto se romperá con (a) cualquier ruta que contenga metacaracteres de espacio en blanco o shell y (b) enlaces simbólicos rotos (no es un problema si solo desea la ruta principal del script en ejecución, suponiendo que (a) no se aplique).
mklement0
Si le preocupan los espacios en blanco, use comillas.
Dave
Por favor, hágalo, aunque eso no abordará (b).
mklement0
Por favor muéstrame un ejemplo de b) donde esto falla. Por definición, un enlace simbólico roto apunta a una entrada de directorio que no existe. El objetivo de este script es resolver enlaces simbólicos en la otra dirección. Si el enlace simbólico no funcionara, no estaría ejecutando el script. Este ejemplo está destinado a demostrar la resolución del script que se está ejecutando actualmente.
Dave
"destinado a demostrar la resolución del script que se está ejecutando actualmente", de hecho, que es un estrechamiento del alcance de la pregunta en la que ha elegido centrarse; eso está perfectamente bien, siempre y cuando tú lo digas. Como no lo hiciste, lo dije en mi comentario. Solucione el problema de las citas, que es un problema independientemente del alcance de la respuesta.
mklement0
2

Nota: Creo que esta es una solución sólida, portátil y lista para usar, que es invariablemente larga por esa misma razón.

A continuación se muestra una secuencia de comandos / función totalmente compatible con POSIX que, por lo tanto, es multiplataforma (también funciona en macOS, que readlinkaún no es compatible a -fpartir de 10.12 (Sierra)): solo utiliza funciones de lenguaje shell POSIX y solo llamadas de utilidad compatibles con POSIX .

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

Puede ejecutar la secuencia de comandos con elsh o la fuente de la función en bash, kshyzsh :

Por ejemplo, dentro de un script puede usarlo de la siguiente manera para obtener el verdadero directorio de origen del script en ejecución, con los enlaces simbólicos resueltos:

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

rreadlink definición de script / función:

El código fue adaptado con gratitud por esta respuesta .
También he creado una bashversión de utilidad independiente basada en aquí , que puede instalar
npm install rreadlink -gsi 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 a la seguridad:

jarno , en referencia a la función que garantiza que commanduna función de alias o shell del mismo nombre no esté sombreada por un nombre, pregunta en un comentario:

¿Qué sucede si unaliaso unsety [se configuran como alias o funciones de shell?

La motivación detrás de rreadlinkgarantizar que commandtenga su significado original es usarlo para evitar ( alias ) conveniencia de alias y funciones que a menudo se usan para sombrear comandos estándar en shells interactivos, como redefinirls para incluir opciones favoritas.

Creo que es seguro decir que a menos que usted está tratando con un entorno sin confianza, malicioso, preocuparse unaliaso unset- o, para el caso, while, do, ... - siendo redefinido no es una preocupación.

Hay algo en lo que la función debe confiar para que tenga su significado y comportamiento originales: no hay forma de evitarlo.
El hecho de que los shells similares a POSIX permitan la redefinición de las palabras clave incorporadas e incluso del lenguaje es inherentemente un riesgo de seguridad (y escribir código paranoico es difícil en general).

Para abordar sus inquietudes específicamente:

La función se basa unaliasy unsettiene su significado original. Tenerlos redefinidos como funciones de shell de una manera que altere su comportamiento sería un problema; la redefinición como un alias no es necesariamente una preocupación, porque al citar (parte de) el nombre del comando (por ejemplo, \unalias) se omiten los alias.

Sin embargo, citando es no una opción para la cáscara palabras clave ( while, for, if, do, ...) y aunque las palabras clave shell hacer de tener prioridad sobre la cáscara funciones , en bashy zshalias tienen la prioridad más alta, por lo que para protegerse de las redefiniciones de concha palabra clave que debe ejecutar unaliascon sus nombres (aunque en shells no interactivos bash (como scripts) los alias no se expanden de forma predeterminada, solo si shopt -s expand_aliasesse llama explícitamente primero).

Para asegurarse de que unalias, como componente integrado, tenga su significado original, \unsetprimero debe usarlo , lo que requiere que unsettenga su significado original:

unsetes un shell integrado , por lo que para asegurarse de que se invoque como tal, deberá asegurarse de que no se redefina como una función . Si bien puede omitir un formulario de alias con comillas, no puede omitir un formulario de función de shell: captura 22.

Por lo tanto, a menos que pueda confiar en unsetque tenga su significado original, por lo que puedo decir, no hay forma garantizada de defenderse contra todas las redefiniciones maliciosas.

mklement0
fuente
En mi experiencia, al citar se omiten los alias, no las funciones de shell, a diferencia de lo que usted dice primero.
jarno
Probé que puedo definir [como un alias en dashy bashy como una función de shell en bash.
jarno
1
Hice mi punto como una pregunta separada .
jarno
@jarno: buenos puntos; He actualizado mi respuesta; Avísame si crees que todavía hay un problema.
mklement0
1
@jarno: puede definir whilecomo una función en bash, kshy zsh(pero no dash), pero solo con la function <name>sintaxis: function while { echo foo; }funciona ( while() { echo foo; }no). Sin embargo, esto no hará sombra a la while palabra clave , porque las palabras clave tienen mayor prioridad que las funciones (la única forma de invocar esta función es como \while). En bashy zsh, los alias tienen mayor precedencia que las palabras clave, por lo que las redefiniciones de alias de las palabras clave las sombrean (pero bashde forma predeterminada solo en shells interactivos , a menos que shopt -s expand_aliasesse llame explícitamente).
mklement0
1

Los scripts de shell comunes a menudo tienen que encontrar su directorio "principal" incluso si se invocan como un enlace simbólico. Por lo tanto, el script debe encontrar su posición "real" desde solo $ 0.

cat `mvn`

en mi sistema imprime un script que contiene lo siguiente, que debería ser una buena pista de lo que 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`
Hugo
fuente
1

Prueba esto:

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

Como me he encontrado con esto muchas veces a lo largo de los años, y esta vez necesitaba una versión portátil puramente 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 hacer que funcione para el 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
Mi respuesta anterior hace exactamente lo mismo en aproximadamente 1/4 del espacio :) Es una secuencia de comandos de shell bastante básica y no merece un repositorio de git.
Dave
1

Para evitar la incompatibilidad de Mac, se me ocurrió

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

No es genial, pero cruza el sistema operativo

Clemens Tolboom
fuente
3
Python 2.6+ está disponible en más sistemas de usuario final que php, por python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"lo que probablemente tendría más sentido. Aún así, para cuando necesite usar Python desde un script de shell, probablemente debería usar Python y ahorrarse el dolor de cabeza de los scripts de shell multiplataforma.
Jonathan Baldwin
No necesita más que el enlace de lectura incorporado sh, dirname y basename.
Dave
@Dave: dirname, basename, y readlinkson externos utilidades , no cáscara de muebles empotrados; dirnamey basenameson parte de POSIX, readlinkno lo es.
mklement0
@ mklement0 - Tienes toda la razón. Son proporcionados por CoreUtils o equivalente. No debería visitar SO después de la 1am. La esencia de mi comentario es que no se requiere PHP ni ningún otro intérprete de lenguaje más allá del instalado en un sistema base. He utilizado el script que proporcioné en esta página en todas las variantes de Linux desde 1997 y MacOS X desde 2006 sin error. El OP no solicitó una solución POSIX. Su entorno específico era Mac OS X.
Dave
@Dave: Sí, es posible hacerlo con las utilidades de stock, pero también es difícil hacerlo (como lo demuestran las deficiencias de su script). Si OS X fuera realmente el foco, entonces esta respuesta está perfectamente bien, y es mucho más simple, dado que phpviene con OS X. Sin embargo, aunque el cuerpo de la pregunta menciona OS X, no está etiquetado como tal, y queda claro que las personas en varias plataformas vienen aquí para obtener respuestas, por lo que vale la pena señalar qué es específico de la plataforma / no POSIX.
mklement0
1

Este es un solucionador de enlaces simbólicos en Bash que funciona si el enlace es un directorio o no:

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
    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 cdy setlas cosas tiene lugar en un subnivel.

merienda
fuente
En realidad, los {} alrededor de () son innecesarios, ya que () cuenta como un "comando compuesto", al igual que {}. Pero aún necesita () después del nombre de la función.
Chris Cogdon
@ChrisCogdon Los {}alrededores ()no son innecesarios si no hay ()detrás del nombre de la función. Bash acepta declaraciones de funciones sin ()porque el shell no tiene listas de parámetros en las declaraciones y no hace llamadas, por ()lo que las declaraciones de funciones con ()no tienen mucho sentido.
solidsnack
0

Aquí presento lo que creo que es una solución multiplataforma (Linux y macOS al menos) a la respuesta que actualmente funciona bien para mí.

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í hay una solución macOS (¿solo?). Posiblemente más adecuado para 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
}
RuneImp
fuente
0

Mi respuesta aquí Bash: ¿cómo obtener la ruta real de un enlace simbólico?

pero en resumen muy útil en scripts:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

Estos son parte de los coreutils de GNU, adecuados para su uso en sistemas Linux.

Para probar todo, ponemos enlace simbólico en / home / test2 /, modificamos algunas cosas adicionales y ejecutamos / llamamos desde el directorio raíz:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

Dónde

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
Arunas Bartisius
fuente
0

En caso de que pwd no pueda usarse (por ejemplo, llamar a scripts desde una ubicación diferente), use realpath (con o sin dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Funciona tanto cuando se llama a través de (múltiples) enlaces simbólicos o cuando se llama directamente al script, desde cualquier ubicación.

nakwa
fuente