Remarque: Je pense que c'est une solution solide, portable et prête à l'emploi, qui est invariablement longue pour cette même raison.
Vous trouverez ci-dessous un script / fonction entièrement compatible POSIX qui est donc multiplateforme (fonctionne également sur macOS, qui readlink
ne prend toujours pas en charge à -f
partir de 10.12 (Sierra)) - il utilise uniquement les fonctionnalités du langage shell POSIX et uniquement les appels d'utilitaires compatibles POSIX .
Il s'agit d'une implémentation portable de GNUreadlink -e
(la version la plus stricte de readlink -f
).
Vous pouvez exécuter le scénario avecsh
ou la source de la fonction dans bash
, ksh
etzsh
:
Par exemple, à l'intérieur d'un script, vous pouvez l'utiliser comme suit pour obtenir le vrai répertoire d'origine du script en cours d'exécution, avec les liens symboliques résolus:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
définition de script / fonction:
Le code a été adapté avec gratitude à partir de cette réponse .
J'ai également créé une bash
version d'utilitaire autonome basée ici , que vous pouvez installer avec
npm install rreadlink -g
, si vous avez Node.js installé.
#!/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 "$@"
Une tangente sur la sécurité:
jarno , en référence à la fonction assurant que la fonction intégrée command
n'est pas masquée par un alias ou une fonction shell du même nom, demande dans un commentaire:
Que faire si unalias
ou unset
et [
sont définis comme alias ou fonctions shell?
La motivation derrière l' rreadlink
assurance que cela command
a sa signification d'origine est de l'utiliser pour contourner les alias de commodité (bénins) et les fonctions souvent utilisées pour masquer les commandes standard dans des shells interactifs, telles que la redéfinition ls
pour inclure les options favorites.
Je pense qu'il est sûr de dire que si vous avez affaire à un non fiable, environnement malveillant, se soucier unalias
ou unset
- ou, pour cette matière, while
, do
, ... - redéfinition n'est pas une préoccupation.
Il y a quelque chose sur lequel la fonction doit s'appuyer pour avoir sa signification et son comportement d'origine - il n'y a aucun moyen de contourner cela.
Le fait que les shells de type POSIX permettent de redéfinir les codes internes et même les mots clés de langage est intrinsèquement un risque pour la sécurité (et l'écriture de code paranoïaque est difficile en général).
Pour répondre spécifiquement à vos préoccupations:
La fonction repose sur unalias
et a unset
sa signification d'origine. Les redéfinir en tant que fonctions shell d'une manière qui modifie leur comportement serait un problème; la redéfinition en tant qu'alias n'est pas nécessairement une préoccupation, car le fait de citer (en partie) le nom de la commande (par exemple \unalias
) contourne les alias.
Cependant, citant est pas une option pour shell mots - clés ( while
, for
, if
, do
, ...) et alors que les mots - clés shell faire préséance sur shell fonctions , dans bash
et zsh
alias ont la plus haute priorité, afin de se prémunir contre redéfinitions shell-mot clé que vous devez exécuter unalias
avec leurs noms (bien que dans les bash
shells non interactifs (tels que les scripts), les alias ne sont pas développés par défaut - uniquement s'ils shopt -s expand_aliases
sont explicitement appelés en premier).
Pour vous assurer que unalias
- en tant que fonction intégrée - a sa signification d'origine, vous devez d'abord l'utiliser \unset
dessus, ce qui nécessite que unset
sa signification d'origine:
unset
est un shell intégré , donc pour vous assurer qu'il est invoqué en tant que tel, vous devez vous assurer qu'il n'est pas redéfini en tant que fonction . Bien que vous puissiez contourner un formulaire d'alias avec des guillemets, vous ne pouvez pas contourner un formulaire de fonction shell - catch 22.
Ainsi, à moins que vous ne puissiez compter sur unset
pour avoir sa signification d'origine, d'après ce que je peux dire, il n'y a aucun moyen garanti de se défendre contre toutes les redéfinitions malveillantes.