Sous Linux, l'utilitaire readlink
accepte une option -f
qui suit des liens supplémentaires. Cela ne semble pas fonctionner sur Mac et éventuellement sur des systèmes BSD. Quel serait l'équivalent?
Voici quelques informations de débogage:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
readlink -f
fait deux choses:
Si vous le souhaitez, vous pouvez simplement créer un script Shell qui utilise le comportement readlink de Vanilla pour obtenir le même résultat. Voici un exemple. Évidemment, vous pouvez l'insérer dans votre propre script à l'endroit où vous souhaitez appeler readlink -f
#!/bin/sh
TARGET_FILE=$1
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
TARGET_FILE=`readlink $TARGET_FILE`
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
done
# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
PHYS_DIR=`pwd -P`
RESULT=$PHYS_DIR/$TARGET_FILE
echo $RESULT
Notez que cela n'inclut pas la gestion des erreurs. D'une importance particulière, il ne détecte pas les cycles de liens symboliques. Un moyen simple de procéder consiste à compter le nombre de fois que vous contournez la boucle et à échouer si vous atteignez un nombre incroyablement élevé, tel que 1 000.
Édité à utiliser pwd -P
au lieu de $PWD
.
Notez que ce script s'attend à être appelé comme ./script_name filename
, pas -f
, changez $1
en $2
si vous voulez pouvoir utiliser avec -f filename
comme GNU readlink.
MacPorts et Homebrew fournissent un package coreutils contenant greadlink
(lien de lecture GNU). Nous remercions Michael Kallweitt de poster sur mackb.com.
brew install coreutils
greadlink -f file.txt
Vous pouvez être intéressé par realpath(3)
, ou par Python os.path.realpath
. Les deux ne sont pas exactement les mêmes. l'appel de la bibliothèque C nécessite la présence de composants de chemin d'accès intermédiaires, contrairement à la version Python.
_$ pwd
/tmp/foo
$ ls -l
total 16
-rw-r--r-- 1 miles wheel 0 Jul 11 21:08 a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 b -> a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 c -> b
$ python -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' c
/private/tmp/foo/a
_
Je sais que vous avez préféré préférer quelque chose de plus léger qu'un autre langage de script, mais si vous ne parvenez pas à compiler un fichier binaire, vous pouvez utiliser Python et ctypes (disponibles sous Mac OS X 10.5) pour envelopper la bibliothèque. appel:
_#!/usr/bin/python
import ctypes, sys
libc = ctypes.CDLL('libc.dylib')
libc.realpath.restype = ctypes.c_char_p
libc.__error.restype = ctypes.POINTER(ctypes.c_int)
libc.strerror.restype = ctypes.c_char_p
def realpath(path):
buffer = ctypes.create_string_buffer(1024) # PATH_MAX
if libc.realpath(path, buffer):
return buffer.value
else:
errno = libc.__error().contents.value
raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value))
if __== '__main__':
print realpath(sys.argv[1])
_
Ironiquement, la version C de ce script devrait être plus courte. :)
Je n'aime pas empiéter sur une autre implémentation, mais il me fallait a) une implémentation Shell portable et pure , et b) couverture par test unitaire , car le nombre de cas Edge pour quelque chose comme ceci est non trivial .
Voir mon projet sur Github pour les tests et le code complet. Ce qui suit est un résumé de la mise en œuvre:
Comme Keith Smith le souligne astucieusement, readlink -f
fait deux choses: 1) résout les liens symboliques de manière récursive, et 2) canonise le résultat, d'où:
realpath() {
canonicalize_path "$(resolve_symlinks "$1")"
}
Tout d'abord, l'implémentation du résolveur de lien symbolique:
resolve_symlinks() {
local dir_context path
path=$(readlink -- "$1")
if [ $? -eq 0 ]; then
dir_context=$(dirname -- "$1")
resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
else
printf '%s\n' "$1"
fi
}
_prepend_path_if_relative() {
case "$2" in
/* ) printf '%s\n' "$2" ;;
* ) printf '%s\n' "$1/$2" ;;
esac
}
Notez qu'il s'agit d'une version légèrement simplifiée de l'implémentation complète . L’implémentation complète ajoute une petite vérification des cycles de lien symbolique , ainsi qu’un petit peu à la sortie.
Enfin, la fonction de canonisation d’un chemin:
canonicalize_path() {
if [ -d "$1" ]; then
_canonicalize_dir_path "$1"
else
_canonicalize_file_path "$1"
fi
}
_canonicalize_dir_path() {
(cd "$1" 2>/dev/null && pwd -P)
}
_canonicalize_file_path() {
local dir file
dir=$(dirname -- "$1")
file=$(basename -- "$1")
(cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}
C'est ça, plus ou moins. Assez simple à coller dans votre script, mais assez compliqué pour que vous soyez complètement fou de pouvoir vous fier à du code ne comportant pas de tests unitaires pour vos cas d'utilisation.
greadlink est le gnu readlink qui implémente -f. Vous pouvez utiliser macports ou autres aussi, je préfère l'homebrew.
Un one-liner en Perl qui fonctionnera à peu près partout sans aucune dépendance externe:
Perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file
Déréférencera les liens symboliques.
L'utilisation dans un script pourrait être comme ceci:
readlinkf(){ Perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
J'ai personnellement créé un script appelé realpath qui ressemble un peu à:
#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
Et ça?
function readlink() {
DIR="${1%/*}"
(cd "$DIR" && echo "$(pwd -P)")
}
FreeBSD et OSX ont une version de stat
dérivée de NetBSD.
Vous pouvez ajuster la sortie avec les commutateurs de format (voir les pages de manuel aux liens ci-dessus).
% cd /service
% ls -tal
drwxr-xr-x 22 root wheel 27 Aug 25 10:41 ..
drwx------ 3 root wheel 8 Jun 30 13:59 .s6-svscan
drwxr-xr-x 3 root wheel 5 Jun 30 13:34 .
lrwxr-xr-x 1 root wheel 30 Dec 13 2013 clockspeed-adjust -> /var/service/clockspeed-adjust
lrwxr-xr-x 1 root wheel 29 Dec 13 2013 clockspeed-speed -> /var/service/clockspeed-speed
% stat -f%R clockspeed-adjust
/var/service/clockspeed-adjust
% stat -f%Y clockspeed-adjust
/var/service/clockspeed-adjust
Certaines versions de stat
sous OS X peuvent ne pas disposer de l'option -f%R
pour les formats. Dans ce cas, -stat -f%Y
peut suffire. L'option -f%Y
affiche la cible d'un lien symbolique, alors que -f%R
indique le chemin absolu correspondant au fichier.
EDIT:
Si vous pouvez utiliser Perl (Darwin/OS X est installé avec les dernières versions de Perl
), alors:
Perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt
marchera.
Voici une fonction de shell portable qui devrait fonctionner dans N’IMPORTE QUEL Bourne comparable au shell. Cela résoudra la ponctuation relative du chemin ".. ou". et déréférencer les liens symboliques.
Si, pour une raison quelconque, vous n'avez pas de commande realpath (1) ou readlink (1), vous pouvez utiliser un alias.
which realpath || alias realpath='real_path'
Prendre plaisir:
real_path () {
OIFS=$IFS
IFS='/'
for I in $1
do
# Resolve relative path punctuation.
if [ "$I" = "." ] || [ -z "$I" ]
then continue
Elif [ "$I" = ".." ]
then FOO="${FOO%%/${FOO##*/}}"
continue
else FOO="${FOO}/${I}"
fi
## Resolve symbolic links
if [ -h "$FOO" ]
then
IFS=$OIFS
set `ls -l "$FOO"`
while shift ;
do
if [ "$1" = "->" ]
then FOO=$2
shift $#
break
fi
done
IFS='/'
fi
done
IFS=$OIFS
echo "$FOO"
}
de plus, juste au cas où quelqu'un serait intéressé, voici comment implémenter le nom de base et le nom de répertoire dans du code Shell à 100% pur:
## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html
# the dir name excludes the least portion behind the last slash.
dir_name () {
echo "${1%/*}"
}
## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html
# the base name excludes the greatest portion in front of the last slash.
base_name () {
echo "${1##*/}"
}
Vous pouvez trouver une version mise à jour de ce code Shell sur mon site google: http://sites.google.com/site/jdisnard/realpath
EDIT: Ce code est disponible selon les termes de la licence à 2 clauses (style FreeBSD). Une copie de la licence peut être trouvée en suivant le lien hypertexte ci-dessus vers mon site.
Le moyen le plus simple de résoudre ce problème et d'activer la fonctionnalité de readlink sur Mac avec Homebrew installé ou FreeBSD est d'installer le paquet 'coreutils'. Peut aussi être nécessaire sur certaines distributions Linux et autres systèmes d’exploitation POSIX.
Par exemple, dans FreeBSD 11, j'ai installé en appelant:
# pkg install coreutils
Sur MacOS avec Homebrew, la commande serait:
$ brew install coreutils
Vous ne savez pas vraiment pourquoi les autres réponses sont si compliquées, c'est tout. Les fichiers ne sont pas à un endroit différent, ils ne sont tout simplement pas encore installés.
Commencer la mise à jour
C'est un problème si fréquent que nous avons créé une bibliothèque Bash 4 à usage libre (licence MIT) appelée realpath-lib . Ceci est conçu pour émuler readlink -f par défaut et inclut deux suites de tests pour vérifier (1) que cela fonctionne pour un système unix donné et (2) contre readlink -f s'il est installé (mais ce n'est pas obligatoire). En outre, il peut être utilisé pour étudier, identifier et décompresser des liens symboliques et des références circulaires profonds et profonds. Il peut donc s'avérer un outil utile pour diagnostiquer les problèmes de fichiers et de répertoires physiques ou symboliques imbriqués en profondeur. Vous pouvez le trouver sur github.com ou bitbucket.org .
Terminer la mise à jour
Une autre solution très compacte et efficace qui ne repose que sur Bash est:
function get_realpath() {
[[ ! -f "$1" ]] && return 1 # failure : file does not exist.
[[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
return 0 # success
}
Cela inclut également un paramètre d’environnement no_symlinks
permettant de résoudre les liens symboliques vers le système physique. Tant que no_symlinks
est défini sur quelque chose, c'est-à-dire no_symlinks='on'
, les liens symboliques sont résolus vers le système physique. Sinon, ils seront appliqués (paramètre par défaut).
Cela devrait fonctionner sur tout système fournissant Bash et renvoyant un code de sortie compatible avec Bash à des fins de test.
Il y a déjà beaucoup de réponses, mais aucune n'a fonctionné pour moi ... C'est donc ce que j'utilise maintenant.
readlink_f() {
local target="$1"
[ -f "$target" ] || return 1 #no nofile
while [ -L "$target" ]; do
target="$(readlink "$target")"
done
echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}
Vraiment indépendant de la plate-forme serait aussi ce R-onliner
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}
Pour imiter réellement readlink -f <path>
, il faudrait utiliser $ 2 au lieu de $ 1.
Étant donné que mon travail est utilisé par des personnes ne disposant pas de BSD Linux ni de MacOS, j'ai opté pour ces alias dans nos scripts de construction (sed
inclus car il a des problèmes similaires):
##
# If you're running macOS, use homebrew to install greadlink/gsed first:
# brew install coreutils
#
# Example use:
# # Gets the directory of the currently running script
# dotfilesDir=$(dirname "$(globalReadlink -fm "$0")")
# alias al='pico ${dotfilesDir}/aliases.local'
##
function globalReadlink () {
# Use greadlink if on macOS; otherwise use normal readlink
if [[ $OSTYPE == darwin* ]]; then
greadlink "$@"
else
readlink "$@"
fi
}
function globalSed () {
# Use gsed if on macOS; otherwise use normal sed
if [[ $OSTYPE == darwin* ]]; then
gsed "$@"
else
sed "$@"
fi
}
Vérification facultative que vous pouvez ajouter pour installer automatiquement les dépendances homebrew + coreutils :
if [[ "$OSTYPE" == "darwin"* ]]; then
# Install brew if needed
if [ -z "$(which brew)" ]; then
/usr/bin/Ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)";
fi
# Check for coreutils
if [ -z "$(brew ls coreutils)" ]; then
brew install coreutils
fi
fi
Je suppose que pour être vraiment "global", il doit vérifier les autres ... mais cela se rapproche probablement de la barre des 80/20.
Mieux vaut tard que jamais, je suppose. J'étais motivé pour développer cela spécifiquement parce que mes scripts Fedora ne fonctionnaient pas sur Mac. Le problème est les dépendances et Bash. Les Mac ne les ont pas, ou s'ils en ont, ils sont souvent ailleurs (un autre chemin). La manipulation du chemin de dépendance dans un script Bash multiplate-forme est au mieux un casse-tête et au pire un risque pour la sécurité. Il est donc préférable d'éviter leur utilisation, si possible.
La fonction get_realpath () ci-dessous est simple, centrée sur Bash et aucune dépendance n'est requise. Je utilise uniquement les construits Bash echo et cd . Il est également assez sécurisé, car tout est testé à chaque étape du processus et renvoie des conditions d'erreur.
Si vous ne voulez pas suivre les liens symboliques, placez set -P au début du script, mais sinon cd devrait résoudre les liens symboliques par défaut. Il a été testé avec des arguments de fichier qui sont {absolu | parent | lien symbolique | local} et renvoie le chemin absolu du fichier. Jusqu'à présent, cela ne nous a pas posé de problèmes.
function get_realpath() {
if [[ -f "$1" ]]
then
# file *must* exist
if cd "$(echo "${1%/*}")" &>/dev/null
then
# file *may* not be local
# exception is ./file.ext
# try 'cd .; cd -;' *works!*
local tmppwd="$PWD"
cd - &>/dev/null
else
# file *must* be local
local tmppwd="$PWD"
fi
else
# file *cannot* exist
return 1 # failure
fi
# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success
}
Vous pouvez combiner cela avec d'autres fonctions get_dirname, get_filename, get_stemname et validate_path. Ceux-ci peuvent être trouvés sur notre dépôt GitHub sous la forme realpath-lib (divulgation complète - c'est notre produit mais nous l'offrons gratuitement à la communauté sans aucune restriction). Il pourrait également servir d’outil pédagogique - il est bien documenté.
Nous avons fait de notre mieux pour appliquer les pratiques dites "modernes de Bash", mais Bash est un sujet important et je suis convaincu qu'il y aura toujours place à amélioration. Il nécessite Bash 4+, mais pourrait fonctionner avec des versions plus anciennes si elles sont toujours présentes.
J'ai écrit n utilitaire realpath pour OS X qui peut fournir les mêmes résultats que readlink -f
.
Voici un exemple:
(jalcazar@mac tmp)$ ls -l a
lrwxrwxrwx 1 jalcazar jalcazar 11 8月 25 19:29 a -> /etc/passwd
(jalcazar@mac tmp)$ realpath a
/etc/passwd
Si vous utilisez MacPorts, vous pouvez l'installer à l'aide de la commande suivante: Sudo port selfupdate && Sudo port install realpath
.
Explication
coreutils est un paquetage brew
qui installe les utilitaires centraux GNU/Linux correspondant à leur implémentation Mac OSX afin que vous puissiez les utiliser.
Vous trouverez peut-être des programmes ou des utilitaires sur votre système mac osx qui semblent similaires à coreutils Linux ("Utilitaires principaux"), mais ils diffèrent à certains égards (notamment en ayant des indicateurs différents).
En effet, les implémentations Mac OSX de ces outils sont différentes. Pour obtenir le comportement original de type GNU/Linux, vous pouvez installer le paquetage coreutils
via le système de gestion de paquets brew
.
Cela installera les utilitaires principaux correspondants, préfixés par g
. Par exemple. pour readlink
, vous trouverez un programme correspondant greadlink
.
Pour que readlink
fonctionne comme l’implémentation GNU readlink
(greadlink
), vous pouvez créer un simple alias après avoir installé coreutils.
Mise en oeuvre
Suivez les instructions à la page https://brew.sh/
brew install coreutils
Vous pouvez placer votre alias dans ~/.bashrc, ~/.bash_profile ou à l’endroit où vous êtes habitué à conserver vos alias bash. Je garde personnellement le mien dans ~/.bashrc
alias readlink=greadlink
Vous pouvez créer des alias similaires pour d'autres coreutils tels que gmv, gdu, gdf, etc. Mais gardez à l'esprit que le comportement GNU sur un ordinateur Mac peut être déroutant pour les autres habitués de travailler avec coreutils natifs ou peut se comporter de manière inattendue sur votre système Mac.