Comment puis-je obtenir le comportement de GNU readlink-f sur un Mac?
sur Linux, l'utilitaire readlink
accepte une option -f
qui suit des liens supplémentaires. Cela ne semble pas fonctionner sur Mac et peut-être des systèmes basés BSD. Ce serait l'équivalent?
voici quelques informations de débogage:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
20 réponses
readlink -f
fait deux choses:
- Il itère le long d'une séquence de liens symboliques jusqu'à ce qu'il trouve un fichier réel.
- il renvoie le nom du fichier canonisé , c'est-à-dire son chemin absolu.
si vous le souhaitez, vous pouvez simplement construire un script shell qui utilise le comportement readlink pour réaliser la même chose. Voici un exemple. Évidemment, vous pouvez insérer ceci dans votre propre script où vous voulez appeler readlink -f
#!/bin/sh
TARGET_FILE=
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 le traitement des erreurs. D'une importance particulière, il ne détecte pas les cycles de symlink. Une façon simple de faire cela serait de compter le nombre de fois où vous faites le tour de la boucle et échouez si vous frappez un nombre étonnamment grand, comme 1000.
modifié pour utiliser pwd -P
au lieu de $PWD
.
notez que ce script s'attend à être appelé comme ./script_name filename
, Non -f
, changer en
si vous voulez pouvoir l'utiliser avec
-f filename
comme GNU readlink.
MacPorts et Homebrew fournissent un paquet coreutils contenant greadlink
(GNU readlink). Crédit à Michael Kallweitt post in mackb.com.
brew install coreutils
greadlink -f file.txt
vous pourriez être intéressé par realpath(3)
, ou de Python os.path.realpath
. Les deux ne sont pas exactement les mêmes; l'appel de bibliothèque C exige que les composants de chemin intermédiaires existent, alors que la version Python n'en a pas.
$ 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 dit préférer quelque chose de plus léger qu'un autre langage de script, mais juste au cas où compiler un binaire est insupportable, vous pouvez utiliser Python et des ctypes (disponibles sur Mac OS X 10.5) pour envelopper l'appel de la bibliothèque:
#!/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 __name__ == '__main__':
print realpath(sys.argv[1])
ironiquement, la version C de ce script devrait être plus courte. :)
je déteste empiler avec encore une autre implémentation, mais j'avais besoin de a) une implémentation portable, pure shell , et b) unité-test de couverture , car le nombre de bordures pour quelque chose comme ça sont 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 astucieusement fait remarquer, readlink -f
fait deux choses: 1) résout symlinks récursivement, et 2) canonicalise le résultat, donc:
realpath() {
canonicalize_path "$(resolve_symlinks "")"
}
tout D'abord, la mise en œuvre du résolveur symlink:
resolve_symlinks() {
local dir_context path
path=$(readlink -- "")
if [ $? -eq 0 ]; then
dir_context=$(dirname -- "")
resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
else
printf '%s\n' ""
fi
}
_prepend_path_if_relative() {
case "" in
/* ) printf '%s\n' "" ;;
* ) printf '%s\n' "/" ;;
esac
}
notez qu'il s'agit d'une version légèrement simplifiée de the full implementation . l'implémentation complète ajoute une petite vérification pour les cycles symlink , ainsi que des massages de la sortie un peu.
enfin, la fonction pour canoniser un chemin:
canonicalize_path() {
if [ -d "" ]; then
_canonicalize_dir_path ""
else
_canonicalize_file_path ""
fi
}
_canonicalize_dir_path() {
(cd "" 2>/dev/null && pwd -P)
}
_canonicalize_file_path() {
local dir file
dir=$(dirname -- "")
file=$(basename -- "")
(cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}
c'est ça, plus ou moins. Assez Simple pour coller dans votre script, mais assez compliqué pour que vous soyez fou de vous fier à n'importe quel code qui n'a pas de tests unitaires pour vos cas d'utilisation.
- Installer homebrew
- Exécuter "brew install coreutils"
- Exécuter "greadlink -f chemin"
greadlink est le gnu readlink qui implémente -F. Vous pouvez utiliser macports ou d'autres aussi, je préfère homebrew.
une simple doublure en perl qui fonctionne presque partout sans aucune dépendance extérieure:
perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file
va déréférencer symlinks.
Utilisation dans un script pourrait être comme ceci:
readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "";}
ABSPATH="$(readlinkf ./non-absolute/file)"
j'ai fait un script appelé realpath personnellement qui ressemble un peu à:
#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
et ça?
function readlink() {
DIR=$(echo "${1%/*}")
(cd "$DIR" && echo "$(pwd -P)")
}
FreeBSD et OSX ont une version de stat
dérivée de NetBSD.
vous pouvez régler la sortie avec des 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 D'OS X de stat
peuvent ne pas avoir l'option -f%R
pour les formats. Dans ce cas, -stat -f%Y
peut suffire. L'option -f%Y
affichera la cible d'un lien symbolique, alors que -f%R
montre le chemin absolu correspondant au fichier.
EDIT:
si vous pouvez utiliser Perl (Darwin / OS X est installé avec les versions récentes de perl
) puis:
perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt
fonctionnera.
Voici une fonction portable shell qui devrait fonctionner dans tout Bourne comparable shell. Il résoudra la ponctuation relative du chemin ".. ou. "et des liens symboliques déréférentiels.
si, pour une raison quelconque, vous n'avez pas de commande realpath(1), ou readlink(1), ceci peut être alias.
which realpath || alias realpath='real_path'
Profiter:
real_path () {
OIFS=$IFS
IFS='/'
for I in
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 [ "" = "->" ]
then FOO=
shift $#
break
fi
done
IFS='/'
fi
done
IFS=$OIFS
echo "$FOO"
}
aussi, juste au cas où quelqu'un est intéressé voici comment mettre en œuvre basename et dirname en 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 la version mise à jour de ce code shell sur mon site google: http://sites.google.com/site/jdisnard/realpath
EDIT: Ce code est autorisé sous les termes de la Licence 2-clause (style freeBSD). Une copie de la licence peut être trouvée en suivant l'hyperlien ci-dessus à mon site.
la manière la plus simple de résoudre ce problème et d'activer les fonctionnalités de readlink sur Mac w/ Homebrew installé ou FreeBSD est d'installer le paquet 'coreutils'. Peut également être nécessaire sur certaines distributions Linux et autres OS POSIX.
par exemple, sous FreeBSD 11, j'ai installé en invoquant:
# pkg install coreutils
sur MacOS avec Homebrew, la commande serait:
$ brew install coreutils
Je ne sais pas vraiment pourquoi les autres réponses sont si compliquées, c'est tout. Les fichiers ne sont pas à un autre endroit, ils ne sont pas encore installés.
Commencer La Mise À Jour
C'est un problème si fréquent que nous avons mis en place une bibliothèque Bash 4 pour une utilisation libre (licence MIT) appelée realpath-lib . Il est conçu pour imiter par défaut readlink-f et comprend deux suites de test pour vérifier (1) qu'il fonctionne pour un système unix donné et (2) contre readlink-f s'il est installé (mais ce n'est pas nécessaire). Outre, il peut être utilisé pour enquêter, identifier et décompresser profonds, liens symboliques cassés et des références circulaires, de sorte qu'il peut être un outil utile pour diagnostiquer profondément imbriqué problèmes physiques ou symboliques de répertoire et de fichiers. Il peut être trouvé à github.com ou bitbucket.org .
Mise À Jour
une autre solution très compacte et efficace qui ne repose sur rien d'autre:
function get_realpath() {
[[ ! -f "" ]] && 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
qui fournit la capacité de résoudre les liens symboliques vers le système physique. Aussi longtemps que no_symlinks
est défini à quelque chose, c'est-à-dire no_symlinks='on'
alors les liens symboliques seront résolus au système physique. Sinon, ils seront appliqués (le paramètre par défaut).
cela devrait fonctionner sur tout système qui fournit Bash, et retournera un code de sortie compatible Bash à des fins de test.
Il y a déjà beaucoup de réponses, mais aucune n'a fonctionné pour moi... C'est ce que j'utilise maintenant.
readlink_f() {
local target=""
[ -f "$target" ] || return 1 #no nofile
while [ -L "$target" ]; do
target="$(readlink "$target")"
done
echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}
mieux vaut tard que jamais, je suppose. J'étais motivé pour développer ceci spécifiquement parce que mes scripts Fedora ne fonctionnaient pas sur le Mac. Le problème est les dépendances et Bash. Les Macs n'en ont pas, ou s'ils en ont, ils sont souvent ailleurs (un autre chemin). La manipulation de chemins de dépendances dans un script de Bash multiplateformes est au mieux un casse - tête et au pire un risque de sécurité-il est donc préférable d'éviter leur utilisation, si possible.
la fonction get_realpath() ci-dessous est simple, Bash-centric, et pas de dépendances sont nécessaires. Je n'utilise que les builtins Bash echo et cd . Il est également assez sûr, car tout est testé à chaque étape du chemin et il renvoie les conditions d'erreur.
si vous ne voulez pas suivre les liens symboliques, alors mettez set-P au début du script, mais autrement cd devrait résoudre les liens symboliques par défaut. Il a été testé avec des arguments de fichier qui sont {absolute | relative | symlink / local} et il renvoie le chemin absolu au fichier. Jusqu'à présent, nous n'avons eu aucun problème avec elle.
function get_realpath() {
if [[ -f "" ]]
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 à notre dépôt GitHub en tant que realpath - lib (pleine divulgation-c'est notre produit, mais nous l'offrons gratuitement à la communauté sans aucune restriction). Il pourrait également servir comme un outil pédagogique - c'est bien documentée.
nous avons fait de notre mieux pour appliquer les pratiques dites "de Bash moderne", mais Bash est un grand sujet et je suis certain qu'il y aura toujours place à l'amélioration. Il nécessite Bash 4+ mais pourrait être fait pour fonctionner avec des versions plus anciennes si elles sont toujours présentes.
Vraiment de la plate-forme indpendent serait également cette R-onliner
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "";}
pour réellement imiter readlink -f <path>
, $2 au lieu de $1 devrait être utilisé.
j'ai écrit un 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 avec la commande suivante: sudo port selfupdate && sudo port install realpath
.
les chemins de readlink sont différents entre mon système et le vôtre. Veuillez essayer de spécifier le chemin complet:
/sw/sbin/readlink -f
Perl a une fonction readlink (par exemple comment copier des liens symboliques en Perl? ). Cela fonctionne sur la plupart des plateformes, y compris OS X:
perl -e "print readlink '/path/to/link'"
par exemple:
$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c
la réponse de @Keith Smith donne une boucle infinie.
voici ma réponse, que je n'utilise que sur SunOS (SunOS manque tellement de commandes POSIX et GNU).
c'est un fichier de script que vous devez mettre dans l'un de vos répertoires $PATH:
#!/bin/sh
! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99
link=""
while [ -L "$link" ]; do
lastLink="$link"
link=$(/bin/ls -ldq "$link")
link="${link##* -> }"
link=$(realpath "$link")
[ "$link" == "$lastlink" ] && echo -e "ERROR: link loop detected on $link" 1>&2 && break
done
echo "$link"