Cómo resolver los enlaces simbólicos en un script de shell
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".
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 halloleo —danorton]
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 bash
basado 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
ounset
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
}