Uwaga: Uważam, że jest to solidne, przenośne, gotowe rozwiązanie, które z tego powodu jest niezmiennie długie .
Poniżej znajduje się w pełni zgodny ze standardem POSIX skrypt / funkcja, która jest zatem wieloplatformowa (działa również na macOS, którego readlink
wciąż nie obsługuje -f
od 10.12 (Sierra)) - używa tylko funkcji języka powłoki POSIX i tylko wywołań narzędzi zgodnych z POSIX .
Jest to przenośna implementacja GNUreadlink -e
(bardziej rygorystyczna wersja readlink -f
).
Można uruchomić skrypt zsh
lub pozyskać funkcję w bash
, ksh
izsh
:
Na przykład w skrypcie możesz użyć go w następujący sposób, aby uzyskać prawdziwy katalog pochodzenia skryptu uruchomionego, z rozwiązanymi dowiązaniami symbolicznymi:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
definicja skryptu / funkcji:
Kod został przyjęty z wdzięcznością z tej odpowiedzi .
Ja również stworzył bash
opartych samodzielną wersję użytkową tutaj , które można zainstalować z
npm install rreadlink -g
, jeśli masz zainstalowany 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 "$@"
Styczna w zakresie bezpieczeństwa:
jarno , w odniesieniu do funkcji zapewniającej, że wbudowana funkcja command
nie jest zaciemniona przez alias lub funkcję powłoki o tej samej nazwie, pyta w komentarzu:
Co jeśli unalias
lub unset
i [
są ustawione jako aliasy lub funkcji powłoki?
Motywacją do rreadlink
zapewnienia, że command
ma to swoje oryginalne znaczenie, jest obejście (łagodnych) wygodnych aliasów i funkcji często używanych do cieniowania standardowych poleceń w interaktywnych powłokach, takich jak redefiniowanie w ls
celu włączenia ulubionych opcji.
Myślę, że to na pewno powiedzieć, że jeśli masz do czynienia z niezaufanych, szkodliwego środowiska, martwić unalias
albo unset
- albo, jeśli o to chodzi, while
, do
, ... - jest nowo nie jest problemem.
Jest coś , na czym funkcja musi polegać, aby mieć swoje pierwotne znaczenie i zachowanie - nie ma na to sposobu.
To, że powłoki podobne do POSIX pozwalają na ponowne zdefiniowanie wbudowanych narzędzi, a nawet słowa kluczowe w języku, jest z natury zagrożeniem bezpieczeństwa (a pisanie kodu paranoicznego jest ogólnie trudne).
Aby w szczególności odpowiedzieć na twoje wątpliwości:
Funkcja polega unalias
i unset
ma swoje oryginalne znaczenie. Ponowne zdefiniowanie ich jako funkcji powłoki w sposób, który zmienia ich zachowanie, stanowiłoby problem; redefinicja jako alias niekoniecznie stanowi problem, ponieważ cytowanie (części) nazwy polecenia (np. \unalias
) pomija aliasy.
Jednak cytowanie jest nie opcja dla powłoki słów kluczowych ( while
, for
, if
, do
, ...) i gdy powłoka słowa kluczowe biorą górę nad powłoki funkcji , w bash
i zsh
aliasy mają najwyższy priorytet, tak aby zabezpieczyć się przed tulejką kluczowe redefiniowaniem należy uruchomić unalias
z ich nazwy (chociaż w nieinteraktywnych bash
powłokach (takich jak skrypty) aliasy nie są domyślnie rozwijane - tylko jeśli shopt -s expand_aliases
zostaną jawnie wywołane jako pierwsze).
Aby mieć pewność, że unalias
- jako wbudowany - ma swoje oryginalne znaczenie, musisz \unset
go najpierw użyć , co wymaga, aby unset
miało to oryginalne znaczenie:
unset
jest wbudowaną powłoką , więc aby upewnić się, że jest wywoływana jako taka, musisz upewnić się, że sama nie zostanie ponownie zdefiniowana jako funkcja . Chociaż możesz ominąć formularz aliasu z cytowaniem, nie możesz ominąć formularza funkcji powłoki - catch 22.
Tak więc, o ile nie mogę polegać na unset
swoim pierwotnym znaczeniu, z tego co mogę powiedzieć, nie ma gwarantowanego sposobu obrony przed wszystkimi złośliwymi przedefiniowaniami.