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.