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) readlinkimmer 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, kshundzsh :
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 commandnicht von einem gleichnamigen Alias oder einer Shell-Funktion überschattet wird, in einem Kommentar:
Was ist, wenn unaliasoder unsetund [als Aliase oder Shell-Funktionen festgelegt sind?
Die Motivation, um rreadlinksicherzustellen, dass dies commandseine 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 unaliasoder 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 unaliasund unsethat 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 bashund zshAliase hat die höchste Priorität, so zum Schutz vor Shell-Schlüsselwort Neudefinitionen Sie ausführen müssen , unaliasmit Ihre Namen (obwohl in nicht interaktiven bash Shells (wie Skripten) Aliase standardmäßig nicht erweitert werden - nur wenn shopt -s expand_aliasessie zuerst explizit aufgerufen werden).
Um sicherzustellen, dass unalias- als eingebautes - seine ursprüngliche Bedeutung hat, müssen Sie \unsetes zuerst verwenden, was erfordert, dass unsetes seine ursprüngliche Bedeutung hat:
unsetist 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 unsetkann 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.