451 Stimmen

Wie kann ich das Verhalten von GNU's readlink -f auf einem Mac erhalten?

Unter Linux wird die readlink Dienstprogramm akzeptiert eine Option -f die zusätzlichen Links folgt. Dies scheint auf Mac- und möglicherweise BSD-basierten Systemen nicht zu funktionieren. Was wäre die Entsprechung?

Hier sind einige Debug-Informationen:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]

1 Stimmen

Etwas spät, aber in Ihrer Frage fehlt ein Hinweis auf die von Ihnen verwendete Shell. Dies ist wichtig, denn readlink kann ein eingebauter oder ein externer Befehl sein.

1 Stimmen

Das würde vielleicht den Unterschied erklären. Ich bin mir aber ziemlich sicher, dass ich bei beiden Gelegenheiten die Bash verwendet habe.

3 Stimmen

Warum ist diese Option auf Macs verboten?

487voto

tomyjwu Punkte 4941

MacPorts und Homebrew bieten eine coreutils Paket mit greadlink (GNU readlink). Dank an Michael Kallweitt Beitrag in mackb.com.

brew install coreutils

greadlink -f file.txt

0 Stimmen

Wenn Sie readlink von anderen Orten als der Bash aufrufen, wäre eine andere Lösung, /usr/bin/readlink zu entfernen und dann einen Link zu /usr/local/bin/greadlink zu erstellen.

11 Stimmen

Bitte tun Sie das nicht, es sei denn, Sie a) schreiben Software für sich selbst und b) wollen sich mit anderen anlegen. Schreiben Sie es so, dass es für jeden "einfach funktioniert".

20 Stimmen

@ching Tun Sie dies stattdessen in Ihrem .bashrc : export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"

207voto

Keith Smith Punkte 3525

readlink -f tut zwei Dinge:

  1. Er durchläuft eine Reihe von Symlinks, bis er eine tatsächliche Datei findet.
  2. Sie gibt die Datei kanonisiert Name, d.h. sein absoluter Pfadname.

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

Beachten Sie, dass dies keine Fehlerbehandlung beinhaltet. Besonders wichtig ist, dass es keine Symlink-Zyklen erkennt. Eine einfache Möglichkeit, dies zu tun, wäre, die Anzahl der Durchläufe der Schleife zu zählen und bei einer unwahrscheinlich großen Zahl, wie z. B. 1.000, abzubrechen.

BEARBEITET zur Verwendung von pwd -P anstelle von $PWD .

Beachten Sie, dass dieses Skript einen Aufruf wie ./script_name filename , nein -f ändern $1 à $2 wenn Sie die Möglichkeit haben wollen, mit -f filename wie GNU readlink.

1 Stimmen

Soweit ich das beurteilen kann, funktioniert das nicht, wenn ein übergeordnetes Verzeichnis des Pfades ein Symlink ist. Wenn z.B. foo -> /var/cux entonces foo/bar wird nicht gelöst werden, weil bar ist kein Link, obwohl foo ist.

1 Stimmen

Ah. Ja. Es ist nicht so einfach, aber Sie können das obige Skript aktualisieren, um damit umzugehen. Ich werde die Antwort entsprechend bearbeiten (umschreiben, wirklich).

0 Stimmen

Nun, ein Link kann überall auf dem Pfad liegen. Ich schätze, das Skript könnte jeden Teil des Pfades durchlaufen, aber dann wird es ein bisschen kompliziert.

47voto

Miles Punkte 29684

Das könnte Sie interessieren realpath(3) oder Pythons os.path.realpath . Die beiden sind nicht genau gleich; der Aufruf der C-Bibliothek erfordert das Vorhandensein von Zwischenpfad-Komponenten, während dies bei der Python-Version nicht der Fall ist.

$ 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ß, Sie sagten, 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 verpacken:

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

0 Stimmen

Ja, realpath ist in der Tat das, was ich will. Aber es scheint ziemlich umständlich zu sein, dass ich eine Binärdatei kompilieren muss, um diese Funktion aus einem Shell-Skript zu erhalten.

5 Stimmen

Warum dann nicht den Python-Einzeiler im Shell-Skript verwenden? (Das unterscheidet sich nicht so sehr von einem einzeiligen Aufruf von readlink selbst, oder?)

3 Stimmen

python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${1}" arbeitet mit Pfaden wie '~/.symlink'

40voto

JinnKo Punkte 863

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

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

Wird Symlinks dereferenzieren.

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

6 Stimmen

Um einen abschließenden Zeilenumbruch zu erhalten, fügen Sie -l , wie perl -MCwd -le 'print Cwd::abs_path shift' ~/non-absolute/file

32voto

Michael Kropat Punkte 13402

Ich hasse es, mit einer weiteren Umsetzung zu kommen, aber ich brauchte eine) eine portable, reine Shell-Implementierung und b) Abdeckung durch Unit-Tests da die Anzahl der Randfälle für so etwas nicht trivial .

Véase mein Projekt auf Github für Tests und vollständigen Code. Im Folgenden finden Sie eine Zusammenfassung der Implementierung:

Wie Keith Smith treffend bemerkt, readlink -f tut zwei Dinge: 1) löst Symlinks rekursiv auf und 2) kanonisiert das Ergebnis, also:

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

Zunächst die Implementierung des Symlink-Auflösers:

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 von die vollständige Umsetzung . Die vollständige Implementierung fügt eine kleine Prüfung auf Symlink-Zyklen hinzu und massiert die Ausgabe ein wenig.

Schließlich die Funktion zur Kanonisierung 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 es in Ihr Skript einzufügen, aber knifflig genug, dass Sie verrückt wären, sich auf einen Code zu verlassen, der keine Unit-Tests für Ihre Anwendungsfälle hat.

4 Stimmen

Dies ist nicht POSIX - es verwendet die nicht-POSIX local Stichwort

1 Stimmen

Dies ist nicht gleichbedeutend mit readlink -f noch von realpath . Unter der Annahme, dass ein Mac mit der coreutils-Formel installiert ist (also mit greadlink verfügbar), versuchen Sie dies mit ln -s ~/Documents /tmp/linkeddir ; greadlink -f /tmp/linkeddir/.. druckt Ihr Home-Verzeichnis aus (es löste die /tmp/linkeddir Link, bevor Sie die .. parent dir ref), aber Ihr Code erzeugt /private/tmp/ como /tmp/linkeddir/.. ist kein Symlink, sondern -d /tmp/linkeddir/.. wahr ist und somit cd /tmp/linkeddir/.. führt Sie zu /tmp , dessen kanonischer Pfad ist /private/tmp . Ihr Code tut, was realpath -L stattdessen tut.

0 Stimmen

Das obige Problem kann gelöst werden mit cd -P in _canonicalize_dir_path

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X