bash - remove - Cómo resolver enlaces simbólicos en un script de shell
symlink windows (16)
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 destino 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 ayudante de C. Desafortunadamente, los shells tienen una tendencia 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 resolver () tal que cuando se ejecute desde el directorio tmp en el ejemplo anterior, resolver ("foo") == "/ Users / greg / tmp / bar".
"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
¿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, 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, de modo que pueda hacer referencia a la configuración u otras secuencias de comandos instaladas junto con ella, uso 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
Podría establecer SOURCE
a cualquier 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 DIR
, que se resolvió en una ubicación real mediante el simple truco que describí por primera vez.
Aquí se explica cómo se puede obtener la ruta real del archivo en MacOS / Unix utilizando 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 de enlaces simbólicos:
DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path(''$0''))")
Creo que esta es la verdadera y definitiva "manera de resolver un enlace simbólico", ya sea un directorio o un no directorio, utilizando 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 todas las cosas de cd
y set
tienen lugar en una subshell
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|"; }
Los scripts de shell comunes a menudo tienen que encontrar su directorio "de inicio" 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, lo que debería ser una buena sugerencia 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`
Para solucionar la incompatibilidad de Mac, se me ocurrió
echo `php -r "echo realpath(''foo'');"`
No es genial pero se cruza el sistema operativo
Prueba esto:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
Reuniendo algunas de las soluciones dadas, sabiendo que readlink está disponible en la mayoría de los sistemas, pero necesita diferentes argumentos, esto me funciona bien en OSX y Debian. No estoy seguro acerca de los sistemas BSD. Tal vez la condición deba ser [[ $OSTYPE != darwin* ]]
para excluir -f
solo de OSX.
#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
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.
Ya que me he encontrado con esto muchas veces a lo largo de los años, y esta vez necesitaba una versión portátil de bash pura 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 comentarios!)
Puede que no sea difícil hacer que funcione para shell bourne normal (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
}
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
usa esta función y te 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
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 un script / función totalmente compatible con POSIX que, por lo tanto, es multiplataforma (también funciona en macOS, cuyo readlink
aún no es compatible con -f
partir de 10.12 (Sierra)). llamadas de utilidad compatibles.
Es una implementación portátil de readlink -e
de GNU (la versión más estricta de readlink -f
).
Puede ejecutar el script con sh
o source la función en bash
, ksh
y zsh
:
Por ejemplo, dentro de un script puede usarlo de la siguiente manera para obtener el verdadero directorio de origen del script de ejecución, con los enlaces simbólicos resueltos:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
script / función de rreadlink
:
El código fue adaptado con gratitud de esta respuesta .
También he creado una versión de utilidad autónoma basada en bash
here , que puede instalar con
npm install rreadlink -g
, si tiene instalado Node.js.
#!/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 seguridad:
jarno , en referencia a la función que garantiza que el command
incorporado no está sombreado por un alias o función de shell del mismo nombre, pregunta en un comentario:
¿Qué
unalias
siunalias
ounset
y[
se configuran como alias o funciones de shell?
La motivación detrás de rreadlink
garantizar que el command
tenga su significado original es usarlo para omitir los alias de conveniencia (benignos) y las funciones que se usan a menudo para sombrear comandos estándar en shells interactivos, como la redefinición de ls
para incluir opciones favoritas.
Creo que es seguro decir que a menos que esté lidiando con un entorno malintencionado y no confiable, preocuparse por unalias
o sin establecer, o, para el caso, while
, do
, ... - ser redefinido no es una preocupación.
Hay algo en lo que la función debe confiar para tener su significado y comportamiento originales, no hay forma de evitarlo.
El hecho de que las carcasas tipo POSIX permiten la redefinición de elementos incorporados e incluso palabras clave de lenguaje es inherentemente un riesgo de seguridad (y escribir código paranoico es difícil en general).
Para abordar sus preocupaciones específicamente:
La función se basa en unalias
y unset
tienen 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 citar (parte de) el nombre del comando (por ejemplo, /unalias
) omite los alias.
Sin embargo, la cita no es una opción para las palabras clave de shell ( while
, for
, if
, do
, ...) y mientras que las palabras clave de shell tienen prioridad sobre las funciones de shell, los alias de bash
y zsh
tienen la mayor prioridad, por lo tanto, para protegerse contra shell la redefinición de palabras clave debe ejecutar unalias
con sus nombres (aunque en alias unalias
no interactivas (como los scripts) los alias no se expanden de forma predeterminada, solo si shopt -s expand_aliases
se llama explícitamente primero).
Para asegurarse de que unalias
, como un componente, tenga su significado original, primero debe usar /unset
, lo que requiere que unset
tenga su significado original:
unset
es un shell incorporado , por lo que para garantizar que se invoque como tal, debe asegurarse de que no se redefine 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 que no tiene un significado original, por lo que puedo decir, no hay una forma garantizada de defenderse contra todas las redefiniciones maliciosas.
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"))/..
readlink -e [filepath]
parece ser exactamente lo que estás pidiendo, acepta una ruta de acceso, resuelve todos los enlaces simbólicos y devuelve la ruta "real" - y es "estándar * nix" que probablemente todos los sistemas ya tengan
readlink -f "$path"
Nota del editor: Lo anterior funciona con el readlink
GNU y el readlink
FreeBSD / PC-BSD / OpenBSD , pero no en OS X a partir de 10.11.
El enlace de readlink
GNU ofrece opciones adicionales relacionadas, como -m
para resolver un enlace simbólico ya sea que exista 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
[ halloleo administrador a continuación del comentario por halloleo - danorton]
Para Mac OS X (a través de al menos 10.11.x), use readlink
sin la opción -f
:
readlink $path
Nota del editor: Esto no resolverá los enlaces simbólicos recursivamente y por lo tanto no informará el objetivo final ; por ejemplo, dado el enlace simbólico a
que apunta a b
, que a su vez apunta a c
, esto solo reportará b
(y no garantizará que se muestre como una ruta absoluta ).
Utilice el siguiente comando perl
en OS X para llenar el vacío de la funcionalidad readlink -f
falta de readlink -f
:
perl -MCwd -le ''print Cwd::abs_path(shift)'' "$path"