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


377

Sous Linux, l' readlinkutilitaire accepte une option -fqui suit des liens supplémentaires. Cela ne semble pas fonctionner sur Mac et éventuellement sur des systèmes basés sur 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 ...]

1
Un peu en retard, mais votre question ne contient aucune mention du shell que vous utilisez. Ceci est pertinent, car il readlinkpeut s'agir d'une commande intégrée ou externe.
0xC0000022L

1
Cela expliquerait peut-être la différence. Je suis à peu près sûr d'avoir utilisé bash aux deux occasions.
troelskn

1
Pourquoi cette option est-elle illégale sur les Mac?
CommaToast

3
Je souhaite vraiment qu'Apple prenne en charge OS X plus de chemins Linux par défaut et aborde des choses comme celle-ci. Ils pouvaient le faire sans rien casser, n'est-ce pas?
CommaToast

1
@CommaToast Eh bien, ils sont livrés avec Perl so ++ :-) ... ouvrez Terminal.app et tapez: touch myfile ; ln -s myfile otherfile ; perl -MCwd=abs_path -le 'print abs_path readlink(shift);' otherfile... dans mon cas, je vois: / Users / cito / myfile`. Ajouté à ma réponse ci-dessous. À votre santé.
G. Cito

Réponses:


181

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 canonisé de ce fichier, c'est-à-dire son nom de chemin absolu.

Si vous le souhaitez, vous pouvez simplement créer un script shell qui utilise le comportement vanlink readlink pour obtenir la même chose. Voici un exemple. De toute évidence, vous pouvez insérer ceci dans votre propre script où vous souhaitez appelerreadlink -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 aucune gestion des erreurs. D'une importance particulière, il ne détecte pas les cycles de liens symboliques. Un moyen simple de le faire serait de compter le nombre de fois que vous parcourez la boucle et échouez si vous atteignez un nombre improbablement élevé, tel que 1 000.

MODIFIÉ à utiliser à la pwd -Pplace de $PWD.

Notez que ce script s'attend à être appelé comme ./script_name filename, non -f, changez $1en $2si vous voulez pouvoir l'utiliser avec -f filenameun lien de lecture GNU similaire.


1
Pour autant que je sache, cela ne fonctionnera pas si un répertoire parent du chemin est un lien symbolique. Par exemple. si foo -> /var/cux, alors foo/barne sera pas résolu, car ce barn'est pas un lien, bien que ce foosoit le cas.
troelskn

1
Ah. Oui. Ce n'est pas aussi simple mais vous pouvez mettre à jour le script ci-dessus pour y faire face. Je vais éditer (réécrire, vraiment) la réponse en conséquence.
Keith Smith

Eh bien, un lien pourrait être n'importe où sur le chemin. Je suppose que le script pourrait parcourir chaque partie du chemin, mais cela devient alors un peu compliqué.
troelskn

1
Merci. Le problème est que $ PWD nous donne le répertoire de travail logique, basé sur les valeurs des liens symboliques que nous avons suivis. Nous pouvons obtenir le vrai répertoire physique avec 'pwd -P'. Il devrait le calculer en poursuivant ".." jusqu'à la racine du système de fichiers. Je mettrai à jour le script dans ma réponse en conséquence.
Keith Smith

12
Excellente solution, mais elle ne traite pas correctement les chemins qui doivent être échappés , tels que les noms de fichiers ou de répertoires avec des espaces intégrés . Pour y remédier, utilisez cd "$(dirname "$TARGET_FILE")" et TARGET_FILE=$(readlink "$TARGET_FILE")et TARGET_FILE=$(basename "$TARGET_FILE")aux endroits appropriés dans le code ci-dessus.
mklement0

439

MacPorts et Homebrew fournissent un paquet coreutils contenant greadlink(GNU readlink). Nous remercions Michael Kallweitt de publier sur mackb.com.

brew install coreutils

greadlink -f file.txt

Si vous appelez readlink depuis des endroits autres que bash, une autre solution serait de supprimer / usr / bin / readlink puis de créer un lien vers / usr / local / bin / greadlink.
chinglun

8
Veuillez ne pas le faire, à moins que vous a) n'écriviez un logiciel pour votre propre b) que vous vouliez jouer avec les autres. Écrivez-le de manière à ce qu'il "fonctionne" pour n'importe qui.
kgadek

14
@ching Faites ceci à la place dans votre .bashrc:export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
bfontaine

1
La question demande l'équivalent sur un système OS X ou BSD, cela ne répond pas à cela. Vous devrez également installer Homebrew. Vous installerez également beaucoup de choses en dehors de GNU Readlink. Si vous écrivez un script pour des installations OS X standard, cette réponse ne vous aidera pas. Vous pouvez utiliser une combinaison de pwd -Pet readlinksur OS X, sans rien installer
Jason S

4
Je voulais que GNU et d'autres utilitaires sur mon Mac fonctionnent si bien, sans préfixe, et facilement accessibles depuis mon utilisateur PATH. Est-ce que ce qui précède a commencé par brew install <package> --default-names. Plusieurs fois, une question sur ce site a reçu une réponse légèrement, ou plus, en dehors des exigences techniques du demandeur, mais dans la même situation, et a toujours été très utile. topbug.net/blog/2013/04/14/…
Pysis

43

Vous pourriez être intéressé par realpath(3), ou Python os.path.realpath. Les deux ne sont pas exactement les mêmes; l'appel à la bibliothèque C nécessite l'existence de composants de chemin 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 dit que vous préféreriez quelque chose de plus léger qu'un autre langage de script, mais juste au cas où la compilation d'un binaire est insupportable, vous pouvez utiliser Python et ctypes (disponibles sur Mac OS X 10.5) pour encapsuler l'appel de 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. :)


Oui, realpathc'est bien ce que je veux. Mais il semble plutôt gênant que je doive compiler un binaire pour obtenir cette fonction à partir d'un script shell.
troelskn

4
Pourquoi ne pas utiliser le Python one-liner dans le script shell alors? (Pas si différent d'un appel sur une seule ligne readlink, n'est-ce pas?)
Télémaque

3
python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${1}"fonctionne avec des chemins comme'~/.symlink'
Wes Turner

@troelskin Je ne savais pas que Perl, Python, etc. étaient "autorisés" !! ... dans ce cas, je vais ajouter perl -MCwd=abs_path -le 'print abs_path readlink(shift);'à ma réponse :-)
G. Cito

27

Je déteste continuer avec une autre implémentation, mais j'avais besoin a) d' une implémentation portable, pure shell , et b) d' une couverture de test unitaire , car le nombre de cas de bord pour quelque chose comme ça n'est pas 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 -ffait deux choses: 1) résout les liens symboliques récursivement, et 2) canonise le résultat, d'où:

realpath() {
    canonicalize_path "$(resolve_symlinks "$1")"
}

Tout d'abord, l'implémentation du résolveur symlink:

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 liens symboliques , ainsi que masse la sortie un peu.

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 plus ou moins ça. Assez simple à coller dans votre script, mais assez délicat pour que vous soyez fou de vous fier à n'importe quel code qui n'a pas de tests unitaires pour vos cas d'utilisation.


3
Ce n'est pas POSIX - il utilise le localmot - clé non-POSIX
go2null

Ce n'est pas équivalent à readlink -f, ni de realpath. En supposant un Mac avec la formule coreutils installée (donc avec greadlinkdisponible), essayez ceci avec ln -s /tmp/linkeddir ~/Documents; greadlink -f /tmp/linkeddir/..affiche votre répertoire personnel (il a résolu le /tmp/linkeddirlien avant d'appliquer la ..référence du répertoire parent), mais votre code produit /private/tmp/tel /tmp/linkeddir/..quel n'est pas un lien symbolique, mais il -d /tmp/linkeddir/..est vrai et cd /tmp/linkeddir/..vous emmène donc vers /tmp, dont le chemin canonique est /private/tmp. Votre code fait ce qu'il realpath -Lfait à la place.
Martijn Pieters

27

Un simple liner en perl qui fonctionnera à coup sûr presque partout sans aucune dépendance externe:

perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file

Déréférencer 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)"

4
pour obtenir une nouvelle ligne de fin, ajoutez -l, commeperl -MCwd -le 'print Cwd::abs_path shift' ~/non-absolute/file
pjvandehaar

26
  1. Installer homebrew
  2. Exécutez "brew install coreutils"
  3. Exécutez "greadlink -f path"

greadlink est le lien de lecture gnu qui implémente -f. Vous pouvez également utiliser macports ou autres, je préfère les homebrews.


2
Je ne vois aucune différence avec la réponse donnée en 2010 stackoverflow.com/a/4031502/695671
Jason S

Si vous devez utiliser ces commandes avec leurs noms normaux, vous pouvez ajouter un répertoire "gnubin" à votre PATH à partir de votre bashrc comme: PATH = "/ usr / local / opt / coreutils / libexec / gnubin: $ PATH"
Denis Trofimov

20

J'ai fait personnellement un script appelé realpath qui ressemble à un petit quelque chose comme:

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

Vous devez changer cela en sys.argv[1]si vous voulez que le script imprime le chemin réel du premier argument. sys.argv[0]imprime simplement le chemin réel du script pyton lui-même, ce qui n'est pas très utile.
Jakob Egger

17
Voici la version alias, pour ~/.profile:alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[1])'"
jwhitlock

Excellente réponse et excellent one-liner @jwhitlock. Depuis l'OP cité readlink -f, voici une version modifiée de l'alias de @jwhitlock pour supporter un -fdrapeau possible : alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[2] if sys.argv[1] == \"-f\" else sys.argv[1])'"
codesniffer

11

Et ça?

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

C'est la seule solution qui fonctionne pour moi; J'ai besoin d'analyser des chemins comme ../../../->/
keflavich

cela ne peut pas fonctionner si vous avez un lien symbolique /usr/bin/par exemple comme le alternativessystème aime avoir.
akostadinov

Ne fonctionne pas pour les fichiers individuels, uniquement pour les répertoires. GNU readlink fonctionne avec les répertoires, fichiers, liens symboliques, etc.
codesniffer

7

Voici une fonction shell portable qui devrait fonctionner dans n'importe quel shell Bourne comparable. Il résoudra la ponctuation relative du chemin ".. ou". et les liens symboliques de déréférencement.

Si, pour une raison quelconque, vous n'avez pas de commande realpath (1) ou readlink (1), cela peut être 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 un 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 clauses (style freeBSD). Une copie de la licence peut être trouvée en suivant l'hyperlien ci-dessus vers mon site.


Real_path renvoie / filename au lieu de / path / to / filename sur Mac OS X Lion dans le shell bash.

@Barry La même chose se produit sur OSX Snow Leopard. Pire, les appels successifs à real_path () concaténent la sortie!
Dominique

@Dominique: pas surprenant, car l'intérieur de la fonction n'est pas exécuté dans son propre sous-shell ... laid et fragile en effet.
0xC0000022L

7

FreeBSD et OSX ont une version statdérivée de NetBSD.

Vous pouvez ajuster la sortie avec des commutateurs de format (voir les pages de manuel sur les 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 stat peuvent ne pas avoir l' -f%Roption pour les formats. Dans ce cas, cela -stat -f%Ypeut suffire. L' -f%Yoption affichera la cible d'un lien symbolique, tandis que -f%Rle chemin d'accès absolu correspondant au fichier sera affiché.

ÉDITER:

Si vous pouvez utiliser Perl (Darwin / OS X est installé avec des versions récentes de perl), alors:

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

marchera.


2
Cela est vrai sur FreeBSD 10, mais pas sur OS X 10.9.
Zanchey

Bonne prise. La page de manuel de statistiques citée concerne OS / X 10.9. L' Roption à -f%est manquante. stat -f%YDonne éventuellement la sortie souhaitée. Je vais ajuster la réponse. Notez que l' statoutil est apparu dans FreeBSD dans la version 4.10 et NetBSD dans la version 1.6.
G. Cito

La commande Perl ne fonctionne pas correctement sur 10.15. Donnez-lui un fichier dans le répertoire courant et il donne le répertoire courant (sans le fichier).
codesniffer

7

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 package 'coreutils'. Peut également être nécessaire sur certaines distributions Linux et autres OS POSIX.

Par exemple, dans FreeBSD 11, j'ai installé en appelant:

# 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 dans un endroit différent, ils ne sont tout simplement pas encore installés.


7
brew install coreutils --with-default-namesPour être plus précis. Ou vous vous retrouverez avec "greadlink" à la place ...
Henk

Je ne vois aucune différence dans les réponses de 2010 stackoverflow.com/a/4031502/695671 et 2012 stackoverflow.com/a/9918368/695671 La question est en fait "... sur Mac et éventuellement sur les systèmes basés sur BSD. l'équivalent soit? " Je ne pense pas que ce soit une très bonne réponse à cette question. De nombreuses raisons, mais vous pouvez cibler des machines Mac OS sans possibilité d'installer quoi que ce soit. pwd -Pest assez simple mais il disparaît parmi beaucoup d'autres réponses, y compris les réponses répétées, d'où le downvote.
Jason S

3

Commencer la mise à jour

C'est un problème si fréquent que nous avons créé une bibliothèque Bash 4 pour une utilisation gratuite (licence MIT) appelée realpath-lib . Ceci est conçu pour émuler readlink -f par défaut et comprend deux suites de tests 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 obligatoire). De plus, il peut être utilisé pour rechercher, identifier et dérouler des liens symboliques profonds et rompus et des références circulaires, il peut donc être un outil utile pour diagnostiquer des problèmes de répertoires et de fichiers physiques ou symboliques profondément imbriqués. Il peut être trouvé sur github.com ou bitbucket.org .

Mettre à jour la fin

Une autre solution très compacte et efficace qui ne repose sur rien d'autre que 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_symlinksqui permet de résoudre les liens symboliques vers le système physique. Tant qu'il no_symlinksest défini sur quelque chose, c'est-à-dire que les no_symlinks='on'liens symboliques seront résolus vers le système physique. Sinon, ils seront appliqués (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.


3

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

1
Cela ne fonctionne pas si $targetest un chemin d'accès, cela ne fonctionne que si c'est un fichier.
MaxGhost

3

Une manière paresseuse qui fonctionne pour moi,

$ brew install coreutils
$ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
$ which readlink
/usr/local/bin/readlink
/usr/bin/readlink

1

Mieux vaut tard que jamais, je suppose. J'étais motivé à développer cela spécifiquement parce que mes scripts Fedora ne fonctionnaient pas sur Mac. Le problème, ce sont les dépendances et Bash. Les Mac n'en ont pas, ou s'ils le font, ils sont souvent ailleurs (autre chemin). La manipulation du chemin de dépendance dans un script Bash multiplateforme 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. J'utilise uniquement l' écho et le cd de Bash builtins . 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 à l'avant 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 {absolus | parent | lien symbolique | local} et il renvoie le chemin absolu du fichier. Jusqu'à présent, nous n'avons eu aucun problème avec cela.

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 dans notre référentiel GitHub sous realpath-lib (divulgation complète - c'est notre produit mais nous le proposons 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 de «Bash moderne», mais Bash est un gros sujet et je suis certain qu'il y aura toujours place à amélioration. Il nécessite Bash 4+ mais pourrait fonctionner avec des versions plus anciennes si elles existent toujours.


1

Étant donné que mon travail est utilisé par des personnes non Linux BSD ainsi que macOS, j'ai opté pour l'utilisation de ces alias dans nos scripts de construction ( sedinclus 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 pourriez 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 faut en vérifier d'autres ... mais cela se rapproche probablement de la marque 80/20.


1

Explication

coreutils est un brewpaquet qui installe les utilitaires principaux GNU / Linux qui correspondent à leur implémentation Mac OSX afin que vous puissiez les utiliser

Vous pouvez trouver des programmes ou des utilitaires sur votre système mac osx qui semblent similaires aux coreutils Linux ("Core Utilities") mais ils diffèrent à certains égards (comme avoir des drapeaux différents).

En effet, l'implémentation Mac OSX de ces outils est différente. Pour obtenir le comportement original de type GNU / Linux, vous pouvez installer le coreutilspackage via le brewsystème de gestion des packages.

Cela installera les utilitaires de base correspondants, préfixés par g. Par exemple pour readlink, vous trouverez un greadlinkprogramme correspondant .

Afin de faire readlinkfonctionner comme l' implémentation GNU readlink( greadlink), vous pouvez créer un alias simple après avoir installé coreutils.

la mise en oeuvre

  1. Installer l'infusion

Suivez les instructions sur 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 partout où vous avez l'habitude de conserver vos alias bash. Personnellement, je garde 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 sachez que le comportement GNU sur une machine mac peut être déroutant pour les autres habitués à travailler avec des coreutils natifs, ou peut se comporter de manière inattendue sur votre système mac.


0

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.


0

Vraiment indépendant de la plate-forme serait également ce R-onliner

readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}

Pour imiter réellement readlink -f <path>, 2 $ au lieu de 1 $ devraient être utilisés.


Sauf que R n'est guère sur aucun système par défaut. Pas encore sur Mac (à partir de 10h15), c'est la raison d'être de cette question.
codesniffer

0

Implémentation conforme POSIX readlink -fpour les scripts shell POSIX

https://github.com/ko1nksm/readlinkf

Ceci est compatible POSIX (pas de bashisme). Il n'utilise ni readlinkni realpath. J'ai vérifié que c'est exactement la même chose en comparant avec GNU readlink -f(voir les résultats des tests ). Il a une gestion des erreurs et de bonnes performances. Vous pouvez remplacer en toute sécurité à partir de readlink -f. La licence est CC0, vous pouvez donc l'utiliser pour n'importe quel projet.

# POSIX compliant version
readlinkf_posix() {
  [ "${1:-}" ] || return 1
  target=$1 max_symlinks=10 CDPATH=''

  [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
  [ -d "${target:-/}" ] && target="$target/"

  cd -P . 2>/dev/null || return 1
  while [ "$max_symlinks" -gt 0 ] && max_symlinks=$((max_symlinks - 1)); do
    if [ ! "$target" = "${target%/*}" ]; then
      cd -P "${target%/*}/" 2>/dev/null || break
      target=${target##*/}
    fi

    if [ ! -L "$target" ]; then
      target="${PWD%/}${target:+/}$target"
      printf '%s\n' "${target:-/}"
      return 0
    fi

    # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
    #   <file mode>, <number of links>, <owner name>, <group name>,
    #   <size>, <date and time>, <pathname of link>, <contents of link>
    # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
    link=$(ls -dl "$target" 2>/dev/null) || break
    target=${link#*" $target -> "}
  done
  return 1
}

Veuillez vous référer au dernier code. Il peut en être corrigé.


-2

Perl a une fonction readlink (par exemple, comment copier des liens symboliques dans Perl? ). Cela fonctionne sur la plupart des plates-formes, 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

5
Cela fait la même chose que readlinksans l' -foption - pas de récursivité, pas de chemin absolu -, vous pouvez donc aussi utiliser readlinkdirectement.
mklement0

-2

La réponse de @Keith Smith donne une boucle infinie.

Voici ma réponse, que j'utilise uniquement sur SunOS (SunOS manque tellement de commandes POSIX et GNU).

C'est un fichier 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="$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"


-5

Les chemins d'accès à readlink sont différents entre mon système et le vôtre. Veuillez essayer de spécifier le chemin complet:

/sw/sbin/readlink -f


1
Et quelle est exactement la différence entre / sw / sbin et / usr / bin? Et pourquoi les deux binaires diffèrent-ils?
troelskn

4
Aha .. donc fink contient un remplacement readlinkcompatible avec GNU. C'est agréable à savoir, mais cela ne résout pas mon problème, car j'ai besoin que mon script s'exécute sur la machine d'autres personnes, et je ne peux pas leur demander d'installer fink pour cela.
troelskn

Vous ne répondez pas à la question d'origine. Veuillez réécrire votre réponse.
Jaime Hablutzel
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.