Question

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 ...]
Était-ce utile?

La solution

readlink -f fait deux choses:

  1. Il parcourt une séquence de liens symboliques jusqu'à ce qu'il trouve un fichier réel.
  2. Il renvoie le nom du fichier canonical de ce fichier & # 8212; c'est-à-dire son chemin absolu.

Si vous le souhaitez, vous pouvez simplement créer un script shell qui utilise le comportement de lecture de vanille à la vanille 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 pwd -P

#!/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. Une façon simple de le faire serait de compter le nombre de fois que vous contournez la boucle et d’échouer si vous atteignez un nombre incroyablement grand, tel que 1 000.

MODIFIÉ pour utiliser $PWD au lieu de ./script_name filename.

Notez que ce script s'attend à être appelé comme -f, non $1, remplacez $2 par -f filename si vous voulez pouvoir utiliser <=> comme .

Autres conseils

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 pourriez être intéressé par realpath(3) , ou os.path.realpath de Python. 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 intermédiaire, 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 dans le cas où la compilation d'un fichier binaire est insupportable, vous pouvez utiliser Python et ctypes (disponibles sous Mac OS X 10.5) pour boucler 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 n'aime pas empiéter sur une autre implémentation, mais il me fallait a) une implémentation en shell pure et portable , et b) couverture de test unitaire , sous la forme des cas marginaux pour quelque chose comme ceci sont non triviaux .

Voir mon projet sur Github pour des tests et du code complet. Ce qui suit est un résumé de la mise en œuvre:

Comme Keith Smith le fait remarquer avec astuce, readlink -f fait deux choses: 1) résout les liens symboliques de manière récursive, et 2) canonise le résultat, par conséquent:

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 la mise en œuvre complète . L’implémentation complète ajoute une petite vérification des cycles de liens symboliques , 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 fou de pouvoir compter sur un code ne comportant pas de tests unitaires pour vos cas d'utilisation.

  1. Installer l'homebrew
  2. Exécuter & "; brasser installer coreutils &";
  3. Exécuter & "; greadlink -f path &";

greadlink est le gnu readlink qui implémente -f. Vous pouvez aussi utiliser macports ou autres, je préfère l’homebrew.

Une simple ligne dans 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 ressembler à ceci:

readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"

J'ai créé personnellement un script appelé realpath qui ressemble un peu à:

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])

Qu'en est-il?

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}

FreeBSD et OSX possède 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 OS X de -f%R peuvent ne pas disposer de l'option -stat -f%Y pour les formats. Dans ce cas, -f%Y peut suffire. L'option perl affiche la cible d'un lien symbolique, tandis que <=> indique le chemin absolu correspondant au fichier.

EDIT:

Si vous parvenez à utiliser Perl (Darwin / OS X est installé avec les dernières versions de <=>), alors:

perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt

fonctionnera.

Voici une fonction de shell portable qui devrait fonctionner dans ANY Bourne comparable shell. Cela résoudra la ponctuation du chemin relatif & Quot; .. ou. & Quot; et déréférencer les liens symboliques.

Si, pour une raison quelconque, vous n'avez pas de commande realpath (1) ou readlink (1), cela peut être aliasé.

which realpath || alias realpath='real_path'

Profitez de

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"
}

Aussi, 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%:

## 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

Sous 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 endroit différent, ils ne sont 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 gratuit (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 si 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 rompus. Il peut donc être 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 à l'adresse github.com ou bitbucket.org .

Terminer la mise à jour

Une autre solution très compacte et efficace qui ne repose que sur Bash est la suivante:

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

}

Ceci 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='on' est défini sur quelque chose, c'est-à-dire <=>, les liens symboliques seront résolus vers le système physique. Sinon, ils seront appliqués (paramètre par défaut).

Ceci 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"
}

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, si possible, leur utilisation.

La fonction get_realpath () ci-dessous est simple, centrée sur Bash et aucune dépendance n'est requise. J'utilise uniquement les éléments intégrés echo et cd de Bash. Il est également assez sécurisé, car tout est testé à chaque étape du processus et renvoie des conditions d'erreur.

Si vous ne souhaitez pas suivre les liens symboliques, placez set -P au début du script, 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. Vous pouvez les trouver dans notre référentiel GitHub sous la forme realpath-lib (divulgation complète - ceci est notre produit mais nous l’offrons gratuitement à la communauté sans aucune restriction). Cela pourrait également servir d’outil pédagogique - c’est bien documenté.

Nous avons fait de notre mieux pour appliquer les pratiques dites «modernes de Bash», mais Bash est un grand sujet et je suis certain qu'il y aura toujours place à amélioration. Il nécessite Bash 4+ mais pourrait fonctionner avec les anciennes versions s’il en existe toujours.

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 utilisant Linux non-BSD ainsi que macOS, j'ai choisi d'utiliser ces alias dans nos scripts de construction (sed inclus car ils ont 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 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 je suis vraiment " global " il doit vérifier les autres ... mais cela approche probablement de la barre des 80/20.

J'ai écrit un utilitaire Realpath pour OS X pouvant 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 brew paquet qui installe les utilitaires centraux GNU / Linux correspondant à leur implémentation Mac OSX afin que vous puissiez les utiliser

.

Vous pouvez trouver sur votre système mac osx des programmes ou des utilitaires qui semblent similaires à coreutils Linux (& "; Core Utilities &";). Cependant, ils diffèrent à certains égards (par exemple, ils ont 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 paquet coreutils via le système de gestion de paquets g.

Ceci installera les principaux utilitaires correspondants, préfixés par readlink. Par exemple. pour greadlink, vous trouverez un brew install coreutils programme correspondant.

Pour que alias readlink=greadlink fonctionne comme pour la mise en œuvre GNU <=> (<=>), vous pouvez créer un simple alias après avoir installé coreutils.

Mise en œuvre

  1. Installer le brassin

Suivez les instructions à l'adresse https://brew.sh/

.
  1. Installez le paquet coreutils

<=>

  1. Créer un alias

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

<=>

Vous pouvez créer des alias similaires pour d’autres coreutils tels que gmv, gdu, gdf, etc. Cependant, sachez que le comportement GNU sur une machine Mac peut être déroutant pour les personnes habituées à travailler avec coreutils natifs ou peut se comporter de manière inattendue sur votre système Mac.

Voici ce que j'utilise:

stat -f %N $your_path

Les chemins d'accès à readlink sont différents entre mon système et le vôtre. Essayez 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 sous SunOS (SunOS manque tellement de commandes POSIX et GNU).

C'est un fichier de script que vous devez placer dans l'un de vos répertoires $ PATH:

#!/bin/sh
! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99

link="$1"
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"
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top