Hinweis: Ich glaube, dass dies eine solide, tragbare und vorgefertigte Lösung ist, die aus genau diesem Grund immer langwierig ist .
Im Folgenden finden Sie ein vollständig POSIX-kompatibles Skript / eine Funktion , die plattformübergreifend ist (funktioniert auch unter macOS, das ab 10.12 (Sierra) readlink
immer noch nicht unterstützt wird -f
). Es werden nur POSIX-Shell-Sprachfunktionen und nur POSIX-kompatible Dienstprogrammaufrufe verwendet .
Es ist eine tragbare Implementierung von GNUsreadlink -e
(der strengeren Version von readlink -f
).
Sie können das ausführen Skript mitsh
oder beziehen Sie die Funktion in bash
, ksh
undzsh
:
In einem Skript können Sie es beispielsweise wie folgt verwenden, um das wahre Ursprungsverzeichnis des laufenden Skripts mit aufgelösten Symlinks abzurufen:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
Skript- / Funktionsdefinition:
Der Code wurde mit Dankbarkeit aus dieser Antwort angepasst .
Ich habe auch eine erstelltes bash
-basierte Stand-alone - Programm - Version hier , die Sie mit installieren können
npm install rreadlink -g
, wenn Sie Node.js installiert.
#!/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 "$@"
Eine Tangente an die Sicherheit:
jarno fragt in Bezug auf die Funktion, die sicherstellt, dass Builtin command
nicht von einem gleichnamigen Alias oder einer Shell-Funktion überschattet wird, in einem Kommentar:
Was ist, wenn unalias
oder unset
und [
als Aliase oder Shell-Funktionen festgelegt sind?
Die Motivation, um rreadlink
sicherzustellen, dass dies command
seine ursprüngliche Bedeutung hat, besteht darin, es zu verwenden, um (harmlose) Komfort- Aliase und -Funktionen zu umgehen, die häufig zum Schattieren von Standardbefehlen in interaktiven Shells verwendet werden, z. B. die Neudefinition ls
, um bevorzugte Optionen einzuschließen.
Ich denke , es ist sicher zu sagen , dass es sei denn , Sie ist der Umgang mit einer nicht vertrauenswürdigen, bösartigen Umgebung, mich darum zu kümmern unalias
oder unset
- oder, was das betrifft, while
, do
, ... - neu definiert wird kein Problem.
Es gibt etwas , auf das sich die Funktion verlassen muss, um ihre ursprüngliche Bedeutung und ihr Verhalten zu erhalten - daran führt kein Weg vorbei.
Dass POSIX-ähnliche Shells die Neudefinition von integrierten Funktionen und sogar von Sprachschlüsselwörtern ermöglichen, ist von Natur aus ein Sicherheitsrisiko (und das Schreiben von paranoidem Code ist im Allgemeinen schwierig).
Um Ihre Bedenken speziell anzusprechen:
Die Funktion beruht auf unalias
und unset
hat ihre ursprüngliche Bedeutung. Es wäre ein Problem, wenn sie in einer Weise als Shell-Funktionen neu definiert würden, die ihr Verhalten verändert. Die Neudefinition als Alias ist nicht unbedingt ein Problem, da das Zitieren (eines Teils) des Befehlsnamens (z. B. \unalias
) Aliase umgeht.
Jedoch unter Angabe ist nicht eine Option für die Shell - Schlüsselwörter ( while
, for
, if
, do
, ...) und während Shell keywords tun hat Vorrang vor Shell - Funktionen , in bash
und zsh
Aliase hat die höchste Priorität, so zum Schutz vor Shell-Schlüsselwort Neudefinitionen Sie ausführen müssen , unalias
mit Ihre Namen (obwohl in nicht interaktiven bash
Shells (wie Skripten) Aliase standardmäßig nicht erweitert werden - nur wenn shopt -s expand_aliases
sie zuerst explizit aufgerufen werden).
Um sicherzustellen, dass unalias
- als eingebautes - seine ursprüngliche Bedeutung hat, müssen Sie \unset
es zuerst verwenden, was erfordert, dass unset
es seine ursprüngliche Bedeutung hat:
unset
ist eine eingebaute Shell. Um sicherzustellen, dass sie als solche aufgerufen wird, müssen Sie sicherstellen, dass sie selbst nicht als Funktion neu definiert wird . Während Sie ein Alias-Formular mit Anführungszeichen umgehen können, können Sie ein Shell-Funktionsformular nicht umgehen - catch 22.
Daher unset
kann es keine garantierte Möglichkeit geben, sich gegen alle böswilligen Neudefinitionen zu verteidigen, es sei denn, Sie können sich darauf verlassen , dass sie ihre ursprüngliche Bedeutung haben.