symbolic remove link generate crear como bash shell scripting symlink

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 acuerdo con los estándares, pwd -P debe devolver la ruta con los enlaces simbólicos resueltos.

La función C char *getcwd(char *buf, size_t size) de unistd.h debería tener el mismo comportamiento.

getcwd pwd


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 si unalias o unset 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"