Wie kann ich das Verhalten von GNUs readlink -f auf einem Mac ermitteln?

377

Unter Linux readlinkakzeptiert das Dienstprogramm eine Option-f , die zusätzlichen Links folgt. Dies scheint auf Mac- und möglicherweise BSD-basierten Systemen nicht zu funktionieren. Was wäre das Äquivalent?

Hier sind einige Debug-Informationen:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
troelskn
quelle
1
Ein bisschen spät, aber in Ihrer Frage fehlt die Erwähnung der von Ihnen verwendeten Shell. Dies ist relevant, da es readlinksich um einen integrierten oder einen externen Befehl handeln kann.
0xC0000022L
1
Das würde den Unterschied vielleicht erklären. Ich bin mir ziemlich sicher, dass ich Bash bei beiden Gelegenheiten verwendet habe.
Troelskn
1
Warum ist diese Option auf Macs illegal?
CommaToast
3
Ich wünschte wirklich, Apple würde OS X dazu bringen, mehr Standard-Linux-Pfade zu unterstützen und solche Dinge anzusprechen. Sie konnten es tun, ohne etwas zu zerbrechen, nicht wahr?
CommaToast
1
@CommaToast Nun, sie werden mit Perl ausgeliefert, also ++ :-) ... öffne Terminal.app und touch myfile ; ln -s myfile otherfile ; perl -MCwd=abs_path -le 'print abs_path readlink(shift);' otherfiletippe : ... in meinem Fall sehe ich: / Users / cito / myfile`. Fügte es meiner Antwort unten hinzu. Prost.
G. Cito

Antworten:

181

readlink -f macht zwei Dinge:

  1. Es iteriert entlang einer Folge von Symlinks, bis eine tatsächliche Datei gefunden wird.
  2. Es gibt den kanonisierten Namen dieser Datei zurück, dh den absoluten Pfadnamen.

Wenn Sie möchten, können Sie einfach ein Shell-Skript erstellen, das das Vanille-Readlink-Verhalten verwendet, um dasselbe zu erreichen. Hier ist ein Beispiel. Natürlich können Sie dies in Ihr eigenes Skript einfügen, in dem Sie anrufen möchtenreadlink -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

Beachten Sie, dass dies keine Fehlerbehandlung beinhaltet. Von besonderer Bedeutung ist, dass Symlink-Zyklen nicht erkannt werden. Eine einfache Möglichkeit, dies zu tun, besteht darin, zu zählen, wie oft Sie die Schleife durchlaufen und fehlschlagen, wenn Sie eine unwahrscheinlich große Zahl treffen, z. B. 1.000.

BEARBEITET, um pwd -Panstelle von zu verwenden $PWD.

Beachten Sie, dass dieses Skript voraussichtlich wie folgt heißt ./script_name filename: Nein -f, ändern Sie $1zu, $2wenn Sie es mit einem -f filenameähnlichen GNU-Readlink verwenden möchten.

Keith Smith
quelle
1
Soweit ich das beurteilen kann, funktioniert das nicht, wenn ein übergeordnetes Verzeichnis des Pfads ein Symlink ist. Z.B. Wenn foo -> /var/cux, dann foo/barwird nicht gelöst, weil bares sich nicht um einen Link foohandelt.
Troelskn
1
Ah. Ja. Es ist nicht so einfach, aber Sie können das obige Skript aktualisieren, um damit umzugehen. Ich werde die Antwort entsprechend bearbeiten (wirklich umschreiben).
Keith Smith
Nun, ein Link könnte sich irgendwo auf dem Weg befinden. Ich denke, das Skript könnte über jeden Teil des Pfades iterieren, aber dann wird es ein bisschen kompliziert.
Troelskn
1
Vielen Dank. Das Problem ist, dass $ PWD uns das logische Arbeitsverzeichnis gibt, basierend auf den Werten in den Symlinks, denen wir gefolgt sind. Wir können das reale physische Verzeichnis mit 'pwd -P' abrufen. Es sollte es berechnen, indem ".." bis zum Stammverzeichnis des Dateisystems verfolgt wird. Ich werde das Skript in meiner Antwort entsprechend aktualisieren.
Keith Smith
12
Tolle Lösung, aber sie behandelt Pfade, die maskiert werden müssen , wie Datei- oder Verzeichnisnamen mit eingebetteten Leerzeichen, nicht richtig . Um dies zu beheben, verwenden Sie cd "$(dirname "$TARGET_FILE")" und TARGET_FILE=$(readlink "$TARGET_FILE")und TARGET_FILE=$(basename "$TARGET_FILE")an den entsprechenden Stellen im obigen Code.
mklement0
438

MacPorts und Homebrew bieten ein Coreutils- Paket mit greadlink(GNU-Readlink). Dank an Michael Kallweitt Beitrag in mackb.com.

brew install coreutils

greadlink -f file.txt
Tomyjwu
quelle
Wenn Sie readlink von anderen Orten als bash aus aufrufen, wäre eine andere Lösung, / usr / bin / readlink zu entfernen und dann einen Link zu / usr / local / bin / greadlink zu erstellen.
Chinglun
8
Bitte nicht, es sei denn, Sie a) schreiben Software für sich selbst b) möchten sich mit anderen anlegen. Schreiben Sie es so, dass es für jeden "einfach funktioniert".
kgadek
14
@ching Tun Sie dies stattdessen in Ihrem .bashrc:export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
bfontaine
1
Die Frage fragt nach dem Äquivalent auf einem OS X- oder BSD-System, dies beantwortet das nicht. Sie müssen auch Homebrew installieren. Abgesehen von GNU Readlink werden Sie auch viele andere Dinge installieren. Wenn Sie ein Skript für Standard-OS X-Installationen schreiben, hilft diese Antwort nicht weiter. Sie können eine Kombination aus pwd -Pund readlinkunter OS X verwenden, ohne etwas zu installieren
Jason S
4
Ich wollte die GNU und andere Dienstprogramme auf meinem Mac, von denen ich glaube, dass sie ohne Präfix so gut funktionieren und von meinen Benutzern aus leicht zugänglich sind PATH. Hat das oben mit angefangen brew install <package> --default-names. Oft wurde eine Frage auf dieser Website geringfügig oder mehr außerhalb der technischen Anforderungen des Fragestellers beantwortet, jedoch innerhalb einer ähnlichen Situation, und war dennoch sehr hilfreich. topbug.net/blog/2013/04/14/…
Pysis
43

Sie könnten interessiert sein realpath(3)oder Pythons os.path.realpath. Die beiden sind nicht genau gleich; Für den Aufruf der C-Bibliothek müssen zwischengeschaltete Pfadkomponenten vorhanden sein, für die Python-Version jedoch nicht.

$ 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

Ich weiß, dass Sie gesagt haben, Sie würden etwas Leichteres als eine andere Skriptsprache bevorzugen, aber nur für den Fall, dass das Kompilieren einer Binärdatei unerträglich ist, können Sie Python und ctypes (verfügbar unter Mac OS X 10.5) verwenden, um den Bibliotheksaufruf zu beenden:

#!/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])

Ironischerweise sollte die C-Version dieses Skripts kürzer sein. :) :)

Meilen
quelle
Ja, realpathist in der Tat was ich will. Aber es scheint ziemlich umständlich, dass ich eine Binärdatei kompilieren muss, um diese Funktion aus einem Shell-Skript zu erhalten.
Troelskn
4
Warum nicht den Python-Einzeiler im Shell-Skript verwenden? (Nicht so anders als ein einzeiliger Anruf für sich readlinkselbst, oder?)
Telemachos
3
python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${1}"arbeitet mit Pfaden wie'~/.symlink'
Wes Turner
@ troelskin Ich wusste nicht, dass Perl, Python usw. "erlaubt" sind !! ... in diesem Fall werde ich perl -MCwd=abs_path -le 'print abs_path readlink(shift);'meine Antwort ergänzen :-)
G. Cito
27

Ich hasse es, mich mit einer weiteren Implementierung zu beschäftigen, aber ich brauchte a) eine tragbare, reine Shell-Implementierung und b) eine Unit-Test-Abdeckung , da die Anzahl der Randfälle für so etwas nicht trivial ist .

Siehe mein Projekt auf Github Tests und vollständigen Code. Was folgt, ist eine Zusammenfassung der Implementierung:

Keith Smith weist scharfsinnig darauf hin, readlink -fdass er zwei Dinge tut: 1) Symlinks rekursiv auflöst und 2) das Ergebnis kanonisiert, daher:

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

Zunächst die Implementierung des Symlink-Resolvers:

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 
}

Beachten Sie, dass dies eine leicht vereinfachte Version der vollständigen Implementierung ist . Die vollständige Implementierung fügt eine kleine Überprüfung für Symlink-Zyklen hinzu und massiert die Ausgabe ein wenig.

Schließlich die Funktion zum Kanonisieren eines Pfades:

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

Das war's mehr oder weniger. Einfach genug, um in Ihr Skript eingefügt zu werden, aber schwierig genug, dass Sie verrückt wären, sich auf Code zu verlassen, der keine Komponententests für Ihre Anwendungsfälle enthält.

Michael Kropat
quelle
3
Dies ist kein POSIX - es verwendet das Nicht-POSIX- localSchlüsselwort
go2null
Dies ist weder gleichbedeutend mit readlink -fnoch von realpath. Angenommen, auf einem Mac ist die Coreutils-Formel installiert (also greadlinkverfügbar). Versuchen Sie dies mit ln -s /tmp/linkeddir ~/Documents; greadlink -f /tmp/linkeddir/..druckt Ihr Home - Verzeichnis (es die aufgelöste /tmp/linkeddirVerbindung , bevor Sie die Anwendung ..Elternverzeichnis ref), aber Ihr Code erzeugt /private/tmp/wie /tmp/linkeddir/..kein symbolischer Link ist, aber -d /tmp/linkeddir/..wahr ist , und so cd /tmp/linkeddir/..nimmt man an /tmp, deren kanonische Weg ist /private/tmp. Ihr Code macht realpath -Lstattdessen das, was er tut.
Martijn Pieters
27

Ein einfacher Einzeiler in Perl, der sicher fast überall ohne externe Abhängigkeiten funktioniert:

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

Wird dereferenzieren Symlinks.

Die Verwendung in einem Skript könnte folgendermaßen aussehen:

readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
JinnKo
quelle
4
Um eine nachfolgende Newline zu erhalten, fügen Sie hinzu -l, wieperl -MCwd -le 'print Cwd::abs_path shift' ~/non-absolute/file
pjvandehaar
26
  1. Installieren Sie Homebrew
  2. Führen Sie "Brew Install Coreutils" aus
  3. Führen Sie "greadlink -f path" aus

greadlink ist der gnu-readlink, der -f implementiert. Sie können auch Macports oder andere verwenden, ich bevorzuge Homebrew.

Andrew
quelle
2
Ich kann keinen Unterschied zu der Antwort sehen, die in 2010 stackoverflow.com/a/4031502/695671
Jason S
Wenn Sie diese Befehle mit ihren normalen Namen verwenden müssen, können Sie Ihrem PATH aus Ihrem bashrc ein "gnubin" -Verzeichnis hinzufügen, wie: PATH = "/ usr / local / opt / coreutils / libexec / gnubin: $ PATH"
Denis Trofimov
20

Ich habe persönlich ein Skript namens realpath erstellt, das ungefähr so ​​aussieht:

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
James
quelle
Sie sollten dies ändern, sys.argv[1]wenn das Skript den Realpfad des ersten Arguments drucken soll. sys.argv[0]druckt nur den tatsächlichen Pfad des Pyton-Skripts selbst aus, was nicht sehr nützlich ist.
Jakob Egger
17
Hier ist die Alias-Version für ~/.profile:alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[1])'"
jwhitlock
Tolle Antwort und toller Einzeiler @jwhitlock. Da das OP zitiert wurde readlink -f, ist hier eine modifizierte Version des Alias ​​von @jwhitlock, um mögliche -fFlaggen zu unterstützen : 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

Was ist damit?

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}
Peeech
quelle
Dies ist die einzige Lösung, die für mich funktioniert. Ich muss Pfade wie ../../../->/
keflavich
Dies kann nicht funktionieren, wenn Sie beispielsweise einen Symlink haben, /usr/bin/wie ihn das alternativesSystem gerne hat.
Akostadinov
Funktioniert nicht für einzelne Dateien, sondern nur für Verzeichnisse. GNU Readlink funktioniert mit Verzeichnissen, Dateien, Symlinks usw.
Codesniffer
7

Hier ist eine tragbare Shell-Funktion, die in JEDEM funktionieren sollte vergleichbaren Bourne- . Die relative Pfadpunktion ".. oder" wird aufgelöst. und Dereferenzierung symbolischer Links.

Wenn Sie aus irgendeinem Grund keinen Befehl realpath (1) oder readlink (1) haben, kann dies ein Alias ​​sein.

which realpath || alias realpath='real_path'

Genießen:

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

Für den Fall, dass sich hier jemand interessiert, erfahren Sie, wie Sie den Basisnamen und den Verzeichnisnamen in 100% reinem Shell-Code implementieren:

## 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##*/}"
}

Eine aktualisierte Version dieses Shell-Codes finden Sie auf meiner Google-Website: http://sites.google.com/site/jdisnard/realpath

BEARBEITEN: Dieser Code ist unter den Bedingungen der 2-Klausel-Lizenz (FreeBSD-Stil) lizenziert. Eine Kopie der Lizenz finden Sie unter dem obigen Hyperlink zu meiner Website.

Masta
quelle
Der real_path gibt unter Mac OS X Lion in der Bash-Shell / filename anstelle von / path / to / filename zurück.
@Barry Dasselbe passiert mit OSX Snow Leopard. Schlimmer noch, aufeinanderfolgende Aufrufe von real_path () verketten die Ausgabe!
Dominique
@Dominique: Kein Wunder, da das Innere der Funktion nicht in einer eigenen Subshell ausgeführt wird ... in der Tat hässlich und zerbrechlich.
0xC0000022L
7

FreeBSD und OSX haben eine statvon NetBSD abgeleitete Version .

Sie können die Ausgabe mit Formatschaltern anpassen (siehe die Handbuchseiten unter den obigen Links).

%  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

Bei einigen OS X-Versionen von stat fehlt möglicherweise die -f%ROption für Formate. In diesem Fall -stat -f%Ykann ausreichen. Die -f%YOption zeigt das Ziel eines Symlinks an, während -f%Rder absolute Pfadname der Datei angezeigt wird.

BEARBEITEN:

Wenn Sie Perl verwenden können (Darwin / OS X wird mit den neuesten Versionen von installiert perl), dann:

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

wird funktionieren.

G. Cito
quelle
2
Dies gilt für FreeBSD 10, jedoch nicht für OS X 10.9.
Zanchey
Guter Fang. Die angegebene Statistik-Handbuchseite ist für OS / X 10.9. Die ROption zu -f%fehlt. Gibt möglicherweise stat -f%Ydie gewünschte Ausgabe. Ich werde die Antwort anpassen. Beachten Sie, dass das statTool in FreeBSD in Version 4.10 und NetBSD in Version 1.6 angezeigt wurde.
G. Cito
Der Perl-Befehl funktioniert unter 10.15 nicht richtig. Geben Sie eine Datei im aktuellen Verzeichnis und das aktuelle Verzeichnis (ohne die Datei).
Codesniffer
7

Der einfachste Weg, um dieses Problem zu lösen und die Funktionalität von Readlink auf einem Mac mit installiertem Homebrew oder FreeBSD zu aktivieren, ist die Installation des Pakets 'coreutils'. Kann auch unter bestimmten Linux-Distributionen und anderen POSIX-Betriebssystemen erforderlich sein.

In FreeBSD 11 habe ich beispielsweise Folgendes installiert:

# pkg install coreutils

Unter MacOS mit Homebrew lautet der Befehl:

$ brew install coreutils

Ich bin mir nicht sicher, warum die anderen Antworten so kompliziert sind, das ist alles. Die Dateien befinden sich nicht an einem anderen Ort, sie sind nur noch nicht installiert.

AveryFreeman
quelle
7
brew install coreutils --with-default-namesum genauer zu sein. Oder Sie werden stattdessen mit "greadlink" enden ...
Henk
Ich kann keinen Unterschied zu den Antworten von 2010 stackoverflow.com/a/4031502/695671 und 2012 stackoverflow.com/a/9918368/695671 feststellen. Die Frage lautet tatsächlich "... auf Mac und möglicherweise BSD-basierten Systemen. Was würde das Äquivalent sein? " Ich glaube nicht, dass dies eine sehr gute Antwort auf diese Frage ist. Viele Gründe, aber Sie zielen möglicherweise auf Mac OS-Computer ab, ohne etwas installieren zu können. pwd -Pist ziemlich einfach, aber es verschwindet unter vielen anderen Antworten, einschließlich wiederholter, daher die Abwertung.
Jason S
3

Update starten

Dies ist ein so häufiges Problem, dass wir eine Bash 4-Bibliothek zur freien Nutzung (MIT-Lizenz) namens realpath-lib zusammengestellt haben . Dies dient standardmäßig zur Emulation von readlink -f und umfasst zwei Testsuiten , um zu überprüfen, (1) ob es für ein bestimmtes Unix-System funktioniert und (2) gegen readlink -f, falls installiert (dies ist jedoch nicht erforderlich). Darüber hinaus kann es verwendet werden, um tiefe, unterbrochene Symlinks und Zirkelverweise zu untersuchen, zu identifizieren und abzuwickeln, sodass es ein nützliches Werkzeug zur Diagnose tief verschachtelter physischer oder symbolischer Verzeichnis- und Dateiprobleme sein kann. Es kann bei github.com oder bitbucket.org gefunden werden .

Update beenden

Eine weitere sehr kompakte und effiziente Lösung, die nur auf Bash basiert, ist:

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

}

Dies umfasst auch eine Umgebungseinstellung no_symlinks, mit der Symlinks zum physischen System aufgelöst werden können. Solange no_symlinksauf etwas eingestellt ist, werden no_symlinks='on'dann Symlinks zum physischen System aufgelöst. Andernfalls werden sie angewendet (Standardeinstellung).

Dies sollte auf jedem System funktionieren, das Bash bereitstellt, und gibt zu Testzwecken einen Bash-kompatiblen Exit-Code zurück.

AsymLabs
quelle
3

Es gibt bereits viele Antworten, aber keine hat bei mir funktioniert ... Das ist es also, was ich jetzt benutze.

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"
}
estani
quelle
1
Dies funktioniert nicht, wenn $targetes sich um einen Pfad handelt, sondern nur, wenn es sich um eine Datei handelt.
MaxGhost
3

Ein fauler Weg, der für mich funktioniert,

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

Besser spät als nie, nehme ich an. Ich war motiviert, dies speziell zu entwickeln, weil meine Fedora-Skripte auf dem Mac nicht funktionierten. Das Problem sind Abhängigkeiten und Bash. Macs haben sie nicht oder wenn doch, sind sie oft woanders (auf einem anderen Weg). Die Manipulation von Abhängigkeitspfaden in einem plattformübergreifenden Bash-Skript bereitet bestenfalls Kopfschmerzen und im schlimmsten Fall ein Sicherheitsrisiko. Vermeiden Sie daher nach Möglichkeit deren Verwendung.

Die Funktion get_realpath () unten ist einfach, Bash-zentriert und es sind keine Abhängigkeiten erforderlich. Ich benutze nur die Bash Builtins Echo und CD . Es ist auch ziemlich sicher, da in jeder Phase des Weges alles getestet wird und Fehlerbedingungen zurückgegeben werden.

Wenn Sie keinen Symlinks folgen möchten, setzen Sie set -P an die Vorderseite des Skripts. Andernfalls sollte cd die Symlinks standardmäßig auflösen. Es wurde mit Dateiargumenten getestet, die {absolute | sind relativ | symlink | local} und gibt den absoluten Pfad zur Datei zurück. Bisher hatten wir keine Probleme damit.

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

}

Sie können dies mit anderen Funktionen get_dirname, get_filename, get_stemname und validate_path kombinieren. Diese finden Sie in unserem GitHub-Repository als realpath-lib (vollständige Offenlegung - dies ist unser Produkt, aber wir bieten es der Community kostenlos und ohne Einschränkungen an). Es könnte auch als Lehrmittel dienen - es ist gut dokumentiert.

Wir haben unser Bestes versucht, um sogenannte "moderne Bash" -Praktiken anzuwenden, aber Bash ist ein großes Thema, und ich bin sicher, dass es immer Raum für Verbesserungen geben wird. Es erfordert Bash 4+, kann aber auch für ältere Versionen verwendet werden, wenn diese noch vorhanden sind.

AsymLabs
quelle
1

Da meine Arbeit von Leuten mit Nicht-BSD-Linux sowie MacOS verwendet wird, habe ich mich für die Verwendung dieser Aliase in unseren Build-Skripten entschieden ( sedenthalten, da es ähnliche Probleme gibt):

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

Optionale Überprüfung, die Sie hinzufügen können, um Homebrew + Coreutils- Abhängigkeiten automatisch zu installieren :

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

Ich nehme an, um wirklich "global" zu sein, muss es andere überprüfen ... aber das kommt wahrscheinlich der 80/20-Marke nahe.

Clounie
quelle
1

Erläuterung

coreutils ist ein brewPaket, das GNU / Linux-Kerndienstprogramme installiert, die der Mac OSX-Implementierung entsprechen, damit Sie diese verwenden können

Möglicherweise finden Sie Programme oder Dienstprogramme auf Ihrem Mac OSX-System, die Linux-Coreutils ("Core Utilities") ähneln, sich jedoch in einigen Punkten unterscheiden (z. B. mit unterschiedlichen Flags).

Dies liegt daran, dass die Mac OSX-Implementierung dieser Tools unterschiedlich ist. Um das ursprüngliche GNU / Linux-ähnliche Verhalten zu erhalten, können Sie das coreutilsPaket über das brewPaketverwaltungssystem installieren.

Dadurch werden die entsprechenden Kerndienstprogramme installiert, denen ein Präfix vorangestellt ist g. Zum Beispiel readlinkfinden Sie ein entsprechendes greadlinkProgramm.

Um eine readlinkLeistung wie bei der Implementierung von GNU readlink( greadlink) zu erzielen, können Sie nach der Installation von coreutils einen einfachen Alias ​​erstellen.

Implementierung

  1. Gebräu installieren

Folgen Sie den Anweisungen unter https://brew.sh/

  1. Installieren Sie das Coreutils-Paket

brew install coreutils

  1. Erstellen Sie einen Alias

Sie können Ihren Alias ​​in ~ / .bashrc, ~ / .bash_profile oder überall dort platzieren, wo Sie es gewohnt sind, Ihre Bash-Aliase zu behalten. Ich persönlich behalte meine in ~ / .bashrc

alias readlink=greadlink

Sie können ähnliche Aliase für andere Coreutils wie gmv, gdu, gdf usw. erstellen. Beachten Sie jedoch, dass das GNU-Verhalten auf einem Mac-Computer für andere, die mit nativen Coreutils arbeiten, verwirrend sein oder sich auf Ihrem Mac-System auf unerwartete Weise verhalten kann.

yosefrow
quelle
0

Ich habe ein Realpath-Dienstprogramm für OS X geschrieben, das die gleichen Ergebnisse liefern kann wie readlink -f.


Hier ist ein Beispiel:

(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


Wenn Sie MacPorts verwenden, können Sie es mit dem folgenden Befehl installieren : sudo port selfupdate && sudo port install realpath.

user454322
quelle
0

Wirklich plattformunabhängig wäre auch dieser R-Onliner

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

Um tatsächlich nachzuahmen readlink -f <path>, müssten 2 USD anstelle von 1 USD verwendet werden.

Holger Brandl
quelle
Außer R ist standardmäßig kaum auf einem System. Noch nicht auf dem Mac (Stand 10.15), worum es bei dieser Frage geht.
Codesniffer
0

POSIX-kompatible readlink -fImplementierung für POSIX-Shell-Skripte

https://github.com/ko1nksm/readlinkf

Dies ist POSIX-konform (kein Bashismus). Es verwendet keinereadlink noch realpath. Ich habe durch Vergleich mit GNU bestätigt, dass es genau dasselbe ist readlink -f(siehe Testergebnisse ). Es hat Fehlerbehandlung und gute Leistung. Sie können sicher von ersetzen readlink -f. Die Lizenz ist CC0, sodass Sie sie für jedes Projekt verwenden können.

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

Bitte beziehen Sie sich auf den neuesten Code. Es kann einige behoben sein.

Koichi Nakashima
quelle
-2

Perl hat eine Readlink-Funktion (z. B. Wie kopiere ich symbolische Links in Perl? ). Dies funktioniert auf den meisten Plattformen, einschließlich OS X:

perl -e "print readlink '/path/to/link'"

Zum Beispiel:

$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c
Jonjlee
quelle
5
Dies geschieht genauso wie readlinkohne die -fOption - keine Rekursion, kein absoluter Pfad -, also können Sie es auch verwendenreadlink direkt können.
mklement0
-2

Die Antwort von @Keith Smith gibt eine Endlosschleife.

Hier ist meine Antwort, die ich nur unter SunOS verwende (SunOS vermisst so viele POSIX- und GNU-Befehle).

Es ist eine Skriptdatei, die Sie in eines Ihrer $ PATH-Verzeichnisse einfügen müssen:

#!/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"
Assgeier
quelle
-2

Das benutze ich:

stat -f %N $your_path

Carlos Nunez
quelle
Dies gibt nicht den vollständigen Pfad aus.
Thom Wiggers
-5

Die Pfade zum Readlink unterscheiden sich zwischen meinem und Ihrem System. Bitte geben Sie den vollständigen Pfad an:

/sw/sbin/readlink -f

ennuikiller
quelle
1
Und was genau ist der Unterschied zwischen / sw / sbin und / usr / bin? Und warum unterscheiden sich die beiden Binärdateien?
Troelskn
4
Aha .. also fink enthält einen ersatz dafür readlinkist gnu kompatibel. Das ist schön zu wissen, aber es löst mein Problem nicht, da ich mein Skript brauche, um auf dem Computer anderer Leute ausgeführt zu werden, und ich kann nicht verlangen, dass sie dafür fink installieren.
Troelskn
Sie beantworten die ursprüngliche Frage nicht. Bitte schreiben Sie Ihre Antwort neu.
Jaime Hablutzel