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 readlink
aún no es compatible a -f
partir 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
, ksh
yzsh
:
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 bash
versión de utilidad independiente basada en aquí , que puede instalar
npm install rreadlink -g
si 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 command
una función de alias o shell del mismo nombre no esté sombreada por un nombre, pregunta en un comentario:
¿Qué sucede si unalias
o unset
y [
se configuran como alias o funciones de shell?
La motivación detrás de rreadlink
garantizar que command
tenga 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 unalias
o 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 unalias
y unset
tiene 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 bash
y zsh
alias tienen la prioridad más alta, por lo que para protegerse de las redefiniciones de concha palabra clave que debe ejecutar unalias
con sus nombres (aunque en shells no interactivos bash
(como 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 componente integrado, tenga su significado original, \unset
primero debe usarlo , lo que requiere que unset
tenga su significado original:
unset
es 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 unset
que tenga su significado original, por lo que puedo decir, no hay forma garantizada de defenderse contra todas las redefiniciones maliciosas.