web-dev-qa-db-fra.com

Comment puis-je obtenir le comportement de readlink -f de GNU sur un Mac?

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 ...]
336
troelskn

readlink -f fait deux choses:

  1. Il itère le long d'une séquence de liens symboliques jusqu'à ce qu'il trouve un fichier réel.
  2. Il retourne ce fichier canonisé nom - c'est-à-dire son chemin absolu.

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.

162
Keith Smith

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
398
tomyjwu

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. :)

42
Miles

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.

25
Michael Kropat
  1. Installer l'homebrew
  2. Exécuter "brasser installer coreutils"
  3. Exécutez "chemin greadlink -f"

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

24
Andrew

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)"
21
JinnKo

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])
18
James

Et ça?

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

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.

8
G. Cito

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.

7
masta

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.

6
AveryFreeman

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.

3
AsymLabs

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

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.

1
Holger Brandl

É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.

1
clounie

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.

1
AsymLabs

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.

0
user454322

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

  1. Installer une infusion

Suivez les instructions à la page https://brew.sh/

  1. Installez le paquet coreutils

brew install 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

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.

0
yosefrow