Nota: credo che questa sia una soluzione solida, portatile e pronta all'uso, che è invariabilmente lunga per questo motivo.
Di seguito è riportato uno script / funzione completamente conforme a POSIX che è quindi multipiattaforma (funziona anche su macOS, il cui readlink
ancora non supporta a -f
partire da 10.12 (Sierra)) - utilizza solo le funzionalità del linguaggio shell POSIX e solo chiamate di utilità conformi a POSIX .
È un'implementazione portatile di GNUreadlink -e
(la versione più rigorosa di readlink -f
).
È possibile eseguire lo script di consh
o fonte la funzione di a bash
, ksh
ezsh
:
Ad esempio, all'interno di uno script è possibile utilizzarlo come segue per ottenere la vera directory di origine dello script in esecuzione, con i collegamenti simbolici risolti:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
definizione script / funzione:
Il codice è stato adattato con gratitudine da questa risposta .
Ho anche creato una bash
versione dell'utilità stand-alone basata qui , che puoi installare
npm install rreadlink -g
se hai installato 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 alla sicurezza:
jarno , in riferimento alla funzione che assicura che builtin command
non sia ombreggiato da una funzione alias o shell con lo stesso nome, chiede in un commento:
Cosa succede se unalias
o unset
e [
sono impostati come alias o funzioni shell?
La motivazione dietro a rreadlink
garantire che command
abbia il suo significato originale è di usarlo per bypassare gli alias e le funzioni di convenienza (benigni) spesso usati per oscurare i comandi standard nelle shell interattive, come ridefinire ls
per includere le opzioni preferite.
Penso che sia giusto dire che a meno che non hai a che fare con un ambiente non attendibile, dannoso, preoccuparsi unalias
o unset
- o, se è per questo, while
, do
, ... - ridefinizione non è una preoccupazione.
C'è qualcosa su cui la funzione deve fare affidamento per avere il suo significato e comportamento originali - non c'è modo di aggirarlo.
Che shell simili a POSIX consentano la ridefinizione dei builtin e persino delle parole chiave del linguaggio è intrinsecamente un rischio per la sicurezza (e scrivere codice paranoico è difficile in generale).
Per rispondere in modo specifico alle tue preoccupazioni:
La funzione si basa unalias
e unset
ha il suo significato originale. Farli ridefinire come funzioni shell in un modo che altera il loro comportamento sarebbe un problema; la ridefinizione come alias non è necessariamente un problema, poiché la citazione (parte del) nome del comando (ad es. \unalias
) ignora gli alias.
Tuttavia, citando è non un'opzione per shell parole chiave ( while
, for
, if
, do
, ...) e mentre le parole chiave shell prevalgono sulle shell funzioni , in bash
e zsh
gli alias hanno la più alta precedenza, in modo da guardia contro ridefinizioni shell-parola chiave è necessario correre unalias
con i loro nomi (anche se nelle shell non interattive bash
(come gli script) gli alias non sono espansi per impostazione predefinita - solo se shopt -s expand_aliases
esplicitamente chiamati per primi).
Per assicurarti che unalias
- come incorporato - abbia il suo significato originale, devi prima usarlo \unset
, il che richiede che unset
abbia il suo significato originale:
unset
è una shell incorporata , quindi per assicurarti che sia invocata come tale, dovresti assicurarti che non venga ridefinita come funzione . Sebbene sia possibile bypassare un modulo alias con la quotazione, non è possibile ignorare un modulo con funzione shell: prendere 22.
Pertanto, a meno che non si possa fare affidamento sul unset
significato originale, da quello che posso dire, non esiste un modo garantito per difendersi da tutte le ridefinizioni dannose.