Jak mogę uzyskać zachowanie readlink -f GNU na komputerze Mac?

376

W systemie Linux readlinknarzędzie akceptuje opcję, -fktóra następuje po dodatkowych linkach. Wydaje się, że to nie działa na komputerach Mac i systemach opartych na BSD. Jaki byłby odpowiednik?

Oto kilka informacji debugowania:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
troelskn
źródło
1
Trochę późno, ale w twoim pytaniu brakuje wzmianki o używanej powłoce. Jest to istotne, ponieważ readlinkmoże to być polecenie wbudowane lub polecenie zewnętrzne.
0xC0000022L
1
To by wyjaśniało różnicę. Jestem całkiem pewien, że użyłem basha za każdym razem.
troelskn
1
Dlaczego ta opcja jest nielegalna na komputerach Mac?
CommaToast,
3
Naprawdę chciałbym, żeby Apple sprawił, że OS X będzie obsługiwał więcej domyślnych ścieżek linuksowych i zajął się takimi sprawami. Mogliby to zrobić bez zepsucia czegokolwiek, prawda?
CommaToast,
1
@CommaToast Cóż, wysyłają z Perlem, więc ++ :-) ... otwórz Terminal.app i wpisz: touch myfile ; ln -s myfile otherfile ; perl -MCwd=abs_path -le 'print abs_path readlink(shift);' otherfile... w moim przypadku widzę: / Users / cito / myfile`. Dodałem to do mojej odpowiedzi poniżej. Twoje zdrowie.
G. Cito,

Odpowiedzi:

181

readlink -f robi dwie rzeczy:

  1. Iteruje po sekwencji dowiązań symbolicznych, aż znajdzie właściwy plik.
  2. Zwraca kanoniczną nazwę tego pliku - tzn. Jego bezwzględną nazwę ścieżki.

Jeśli chcesz, możesz po prostu zbudować skrypt powłoki, który używa waniliowego zachowania readlink, aby osiągnąć to samo. Oto przykład. Oczywiście możesz wstawić to do własnego skryptu, do którego chcesz zadzwonić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

Pamiętaj, że nie obejmuje to obsługi błędów. Szczególnie ważne jest to, że nie wykrywa cykli dowiązań symbolicznych. Prostym sposobem na zrobienie tego byłoby policzenie, ile razy ominąłeś pętlę i nie powiodło się, jeśli trafisz nieprawdopodobnie dużą liczbę, na przykład 1000.

EDYCJA użyć pwd -Pzamiast $PWD.

Zauważ, że ten skrypt oczekuje, że zostanie wywołany jak ./script_name filename, nie -f, zmień $1na, $2jeśli chcesz mieć możliwość korzystania z -f filenamepodobnego linku do odczytu GNU.

Keith Smith
źródło
1
O ile wiem, to nie zadziała, jeśli katalog macierzysty ścieżki jest dowiązaniem symbolicznym. Na przykład. jeśli foo -> /var/cux, to foo/barnie zostanie rozwiązany, ponieważ barnie jest to link, chociaż tak foojest.
troelskn
1
Ach Tak. To nie jest tak proste, ale możesz zaktualizować powyższy skrypt, aby sobie z tym poradzić. Przeredaguję (przepiszę, naprawdę) odpowiednio odpowiedź.
Keith Smith
Link może być w dowolnym miejscu na ścieżce. Wydaje mi się, że skrypt może iterować każdą część ścieżki, ale wtedy staje się to trochę skomplikowane.
troelskn
1
Dzięki. Problem polega na tym, że $ PWD podaje nam logiczny katalog roboczy, oparty na wartościach w dowiązaniach symbolicznych, które śledziliśmy. Możemy pobrać prawdziwy katalog fizyczny za pomocą „pwd -P”. Powinien on go obliczyć, ścigając „..” do katalogu głównego systemu plików. Zaktualizuję odpowiednio skrypt w odpowiedzi.
Keith Smith
12
Świetne rozwiązanie, ale nie radzi sobie dobrze ze ścieżkami, które wymagają zmiany znaczenia , takimi jak nazwy plików lub katalogów z osadzonymi spacjami . Aby zaradzić tym, stosowanie cd "$(dirname "$TARGET_FILE")" i TARGET_FILE=$(readlink "$TARGET_FILE")i TARGET_FILE=$(basename "$TARGET_FILE")w odpowiednich miejscach w powyższym kodzie.
mklement0
438

MacPorts i Homebrew zapewniają pakiet coreutils zawierający greadlink(readlink GNU). Podziękowania dla posta Michaela Kallweitta na mackb.com.

brew install coreutils

greadlink -f file.txt
tomyjwu
źródło
Jeśli wywołujesz readlink z miejsc innych niż bash, innym rozwiązaniem byłoby usunięcie / usr / bin / readlink, a następnie utworzenie łącza do / usr / local / bin / greadlink.
chinglun,
8
Nie rób tego, chyba że: a) piszesz własne oprogramowanie, b) chcesz zadzierać z innymi. Napisz to w taki sposób, aby „po prostu działało” dla każdego.
kgadek
14
@ching Zrób to zamiast tego w .bashrc:export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
bfontaine
1
Pytanie dotyczy odpowiednika w systemie OS X lub BSD, to nie odpowiada na to. Będziesz także musiał zainstalować Homebrew. Będziesz także instalował wiele rzeczy oprócz GNU Readlink. Jeśli piszesz skrypt dla standardowych instalacji OS X, ta odpowiedź nie pomoże. Można użyć kombinacji pwd -Pi tym readlinkna OS X, bez instalowania czegokolwiek
Jason S
4
Chciałem GNU i innych narzędzi na moim Macu, które moim zdaniem działają tak dobrze, bez prefiksu i łatwo dostępne dla mojego użytkownika PATH. Czy powyższe zaczyna się od brew install <package> --default-names. Wiele razy na pytanie na tej stronie udzielono niewielkiej odpowiedzi, lub więcej, poza technicznymi wymaganiami pytającego, ale w podobnej sytuacji i nadal było bardzo pomocne. topbug.net/blog/2013/04/14/…
Pysis
43

Możesz być zainteresowany realpath(3)Pythonem os.path.realpath. Te dwa nie są dokładnie takie same; wywołanie biblioteki C wymaga istnienia komponentów ścieżki pośredniej, podczas gdy wersja Python nie.

$ 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

Wiem, że powiedziałeś, że wolisz coś lżejszego niż inny język skryptowy, ale na wypadek, gdyby kompilacja pliku binarnego była niemożliwa, możesz użyć Pythona i ctypów (dostępnych w Mac OS X 10.5) do zawinięcia wywołania biblioteki:

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

Jak na ironię, wersja C tego skryptu powinna być krótsza. :)

Miles
źródło
Tak, realpathrzeczywiście tego chcę. Ale wydaje się dość niezręczne, że muszę skompilować plik binarny, aby uzyskać tę funkcję ze skryptu powłoki.
troelskn
4
Dlaczego więc nie użyć jednowierszowego języka Python w skrypcie powłoki? (Nie różni się tak bardzo od połączenia z jedną linią doreadlink , prawda?)
Telemachus,
3
python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${1}"działa ze ścieżkami typu'~/.symlink'
Wes Turner
@troelskin Nie zdawałem sobie sprawy, że Perl, Python itp. są „dozwolone” !! ... w takim razie dodam perl -MCwd=abs_path -le 'print abs_path readlink(shift);'do mojej odpowiedzi :-)
G. Cito
27

Nienawidzę nakładać na siebie kolejnej implementacji, ale potrzebowałem a) przenośnej implementacji czystej powłoki ib) pokrycia testem jednostkowym , ponieważ liczba przypadków brzegowych dla czegoś takiego nie jest banalna .

Zobacz mój projekt na Github dla testów i pełnego kodu. Poniżej znajduje się streszczenie implementacji:

Jak trafnie zauważa Keith Smith, readlink -frobi dwie rzeczy: 1) rozwiązuje rekursywnie dowiązania symboliczne i 2) kanonizuje wynik, stąd:

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

Po pierwsze, implementacja translatora 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 
}

Pamiętaj, że jest to nieco uproszczona wersja pełnej implementacji . Pełna implementacja dodaje małą kontrolę cykli dowiązań symbolicznych , a także nieco masuje dane wyjściowe.

Na koniec funkcja kanonizacji ścieżki:

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

To mniej więcej tyle. Wystarczająco prosty do wklejenia do skryptu, ale na tyle podstępny, że oszalałbyś na punkcie dowolnego kodu, który nie ma testów jednostkowych dla twoich przypadków użycia.

Michael Kropat
źródło
3
To nie jest POSIX - używa localsłowa kluczowego innego niż POSIX
go2null
To nie jest równoważne readlink -fani realpath. Zakładając komputer Mac z zainstalowaną formułą coreutils (więc z greadlinkdostępną), spróbuj tego z ln -s /tmp/linkeddir ~/Documents; greadlink -f /tmp/linkeddir/..wypisuje twój katalog domowy (rozwiązał /tmp/linkeddirlink przed zastosowaniem ..nadrzędnego katalogu referencyjnego), ale twój kod tworzy, /private/tmp/jak /tmp/linkeddir/..nie jest dowiązaniem symbolicznym, ale -d /tmp/linkeddir/..jest prawdą i dlatego cd /tmp/linkeddir/..zabiera cię do /tmp, którego kanoniczną ścieżką jest /private/tmp. realpath -LZamiast tego Twój kod robi to , co robi.
Martijn Pieters
27

Prosty liniowiec w Perlu, który z pewnością działa prawie wszędzie bez żadnych zewnętrznych zależności:

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

Odrzuci dowiązania symboliczne.

Użycie w skrypcie może wyglądać następująco:

readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
JinnKo
źródło
4
aby uzyskać końcową linię, dodaj -l, na przykładperl -MCwd -le 'print Cwd::abs_path shift' ~/non-absolute/file
pjvandehaar
26
  1. Zainstaluj homebrew
  2. Uruchom „parzenie instalacji coreutils”
  3. Uruchom „greadlink -f path”

greadlink to readlink gnu, który implementuje -f. Możesz używać Macports i innych, wolę homebrew.

Andrzej
źródło
2
Nie widzę żadnej różnicy w stosunku do odpowiedzi udzielonej w 2010 stackoverflow.com/a/4031502/695671
Jason S
Jeśli potrzebujesz użyć tych poleceń z ich normalnymi nazwami, możesz dodać katalog „gnubin” do PATH z twojego bashrc, np .: PATH = "/ usr / local / opt / coreutils / libexec / gnubin: $ PATH"
Denis Trofimov
20

Zrobiłem osobiście skrypt o nazwie realpath, który wygląda trochę jak:

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
James
źródło
Powinieneś to zmienić, sys.argv[1]jeśli chcesz, aby skrypt wypisał rzeczywistą ścieżkę pierwszego argumentu. sys.argv[0]po prostu drukuje prawdziwą ścieżkę samego skryptu pyton, co nie jest zbyt przydatne.
Jakob Egger,
17
Oto wersja pseudonimu dla ~/.profile:alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[1])'"
jwhitlock
Świetna odpowiedź i świetny one-liner @jwhitlock. Od czasu cytowania OP readlink -f, oto zmodyfikowana wersja aliasu @jwhitlock do obsługi ewentualnej -fflagi: alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[2] if sys.argv[1] == \"-f\" else sys.argv[1])'"
codsffffer
11

A co z tym?

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}
Peeech
źródło
To jedyne rozwiązanie, które działa dla mnie; Muszę przeanalizować ścieżki, takie jak ../../../->/
keflavich,
nie może to działać, jeśli masz dowiązanie symboliczne, /usr/bin/takie jak na przykład alternativessystem.
akostadinov,
Nie działa w przypadku pojedynczych plików, tylko katalogów. GNU readlink współpracuje z katalogami, plikami, dowiązaniami symbolicznymi itp.
codeniffer
7

Tutaj jest przenośnym funkcja powłoki, które powinny pracować w JAKAŚ Bourne porównywalną powłokę. Rozwiąże względną interpunkcję ścieżki „.. lub”. i dereferencyjne dowiązania symboliczne.

Jeśli z jakiegoś powodu nie masz polecenia realpath (1) lub readlink (1), można to zmienić.

which realpath || alias realpath='real_path'

Cieszyć się:

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

także, na wypadek, gdyby ktoś był zainteresowany tym, jak zaimplementować basename i dirname w 100% czystym kodzie powłoki:

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

Zaktualizowaną wersję tego kodu powłoki można znaleźć na mojej stronie Google: http://sites.google.com/site/jdisnard/realpath

EDYCJA: Ten kod jest licencjonowany na warunkach licencji 2-klauzulowej (styl FreeBSD). Kopię licencji można znaleźć, postępując zgodnie z powyższym hiperłączem do mojej witryny.

masta
źródło
Real_path zwraca / filename zamiast / path / to / filename w Mac OS X Lion w powłoce bash.
@Barry To samo dzieje się w OSX Snow Leopard. Co gorsza, kolejne wywołania konkatenacji danych wyjściowych real_path ()!
Dominique,
@Dominique: nic dziwnego, ponieważ wnętrze funkcji nie działa we własnej podpowłoce ... naprawdę brzydkie i kruche.
0xC0000022L
7

FreeBSD i OSX mają wersję statpochodzącą z NetBSD.

Możesz wyregulować wyjście za pomocą przełączników formatu (patrz strony podręcznika pod powyższymi linkami).

%  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

Niektóre wersje OS X stat mogą nie mieć -f%Ropcji formatów. W takim przypadku -stat -f%Ymoże wystarczyć. -f%YWariant pokazuje cel, podczas gdy miejsce linku -f%Rpokazuje absolutnym ścieżka odpowiadającego plikowi.

EDYTOWAĆ:

Jeśli możesz używać Perla (Darwin / OS X jest instalowany z najnowszymi wersjami perl), to:

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

będzie działać.

G. Cito
źródło
2
Dotyczy to FreeBSD 10, ale nie dotyczy OS X 10.9.
Zanchey
Dobry chwyt Stat strona podręcznika cytowany jest dla OS / X 10.9. ROpcja -f%brakuje. Prawdopodobnie stat -f%Ydaje pożądaną wydajność. Dostosuję odpowiedź. Zauważ, że statnarzędzie pojawiło się we FreeBSD w wersji 4.10 i NetBSD w wersji 1.6.
G. Cito,
Polecenie Perl nie działa poprawnie w wersji 10.15. Daj mu plik w bieżącym katalogu, a on poda bieżący katalog (bez pliku).
codeniffer
7

Najprostszym sposobem rozwiązania tego problemu i włączenia funkcji readlink na Macu z zainstalowanym Homebrew lub FreeBSD jest zainstalowanie pakietu „coreutils”. Może być również konieczne w niektórych dystrybucjach Linuksa i innych systemach operacyjnych POSIX.

Na przykład w FreeBSD 11 zainstalowałem, wywołując:

# pkg install coreutils

W systemie MacOS z Homebrew polecenie wyglądałoby następująco:

$ brew install coreutils

Nie jestem pewien, dlaczego inne odpowiedzi są tak skomplikowane, to wszystko. Pliki nie są w innym miejscu, po prostu nie są jeszcze zainstalowane.

AveryFreeman
źródło
7
brew install coreutils --with-default-namesbyć bardziej specyficznym. Albo skończysz na „greadlink” zamiast…
Henk
Nie widzę żadnej różnicy w odpowiedziach z 2010 stackoverflow.com/a/4031502/695671 i 2012 stackoverflow.com/a/9918368/695671 Pytanie brzmi „... na komputerach Mac i prawdopodobnie systemach opartych na BSD. odpowiednik być? ” Nie wierzę, że to bardzo dobra odpowiedź na to pytanie. Wiele powodów, ale możesz atakować komputery Mac OS bez możliwości instalacji. pwd -Pjest dość proste, ale znika wśród wielu innych odpowiedzi, w tym powtarzających się, stąd opinia negatywna.
Jason S
3

Rozpocznij aktualizację

Jest to tak częsty problem, że stworzyliśmy bibliotekę Bash 4 do bezpłatnego użytku (licencja MIT) o nazwie realpath-lib . Ma to na celu domyślną emulację readlink -f i obejmuje dwa pakiety testowe do sprawdzenia (1), czy działa on dla danego systemu unix i (2) względem readlink -f, jeśli jest zainstalowany (ale nie jest to wymagane). Ponadto można go używać do badania, identyfikowania i rozwijania głębokich, zepsutych dowiązań symbolicznych i odnośników cyklicznych, dzięki czemu może być użytecznym narzędziem do diagnozowania głęboko zagnieżdżonych problemów fizycznych lub symbolicznych katalogów i plików. Można go znaleźć na stronie github.com lub bitbucket.org .

Zakończ aktualizację

Kolejnym bardzo kompaktowym i wydajnym rozwiązaniem, które nie polega na niczym innym, jak na Bash, jest:

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

}

Obejmuje to również ustawienie środowiska, no_symlinksktóre umożliwia rozpoznawanie dowiązań symbolicznych do systemu fizycznego. Tak długo, jak długo no_symlinksjest to ustawione, tzn. no_symlinks='on'Wtedy dowiązania symboliczne będą rozstrzygane w systemie fizycznym. W przeciwnym razie zostaną zastosowane (ustawienie domyślne).

Powinno to działać na każdym systemie, który udostępnia Bash, i zwróci kod wyjścia zgodny z Bash do celów testowych.

AsymLabs
źródło
3

Jest już wiele odpowiedzi, ale żadna nie działała dla mnie ... Więc teraz tego używam.

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
źródło
1
To nie działa, jeśli $targetjest ścieżką, działa tylko, jeśli jest to plik.
MaxGhost
3

Leniwy sposób, który działa dla mnie,

$ brew install coreutils
$ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
$ which readlink
/usr/local/bin/readlink
/usr/bin/readlink
Izana
źródło
1

Lepiej późno niż wcale. Byłem zmotywowany do opracowania tego specjalnie, ponieważ moje skrypty Fedory nie działały na komputerze Mac. Problemem są zależności i Bash. Komputery Mac nie mają ich, a jeśli tak, to często znajdują się gdzie indziej (inna ścieżka). Manipulowanie ścieżkami zależności w wieloplatformowym skrypcie Bash to w najlepszym wypadku ból głowy, aw najgorszym przypadku zagrożenie bezpieczeństwa - najlepiej, jeśli to możliwe, unikać ich używania.

Poniższa funkcja get_realpath () jest prosta, skoncentrowana na Bash i nie są wymagane żadne zależności. Korzystam tylko z echa i płyty CD wbudowanych w Bash . Jest również dość bezpieczny, ponieważ wszystko jest testowane na każdym etapie i zwraca warunki błędu.

Jeśli nie chcesz podążać za dowiązaniami symbolicznymi, umieść zestaw -P na początku skryptu, ale w przeciwnym razie cd powinien domyślnie rozpoznać dowiązania symboliczne. Został przetestowany z argumentami pliku, które są {absolutne | krewny | dowiązanie symboliczne | local} i zwraca bezwzględną ścieżkę do pliku. Do tej pory nie mieliśmy z tym żadnych problemów.

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

}

Możesz to połączyć z innymi funkcjami nazwa_katalogu get, nazwa_pliku get, nazwa_pliku get i nazwa_ścieżki. Można je znaleźć w naszym repozytorium GitHub jako realpath-lib (pełne ujawnienie - to nasz produkt, ale oferujemy go społeczności bez żadnych ograniczeń). Może również służyć jako narzędzie instruktażowe - jest dobrze udokumentowane.

Staraliśmy się jak najlepiej stosować tak zwane „nowoczesne Bash”, ale Bash to duży temat i jestem pewien, że zawsze będzie miejsce na poprawę. Wymaga Bash 4+, ale może być przystosowany do pracy ze starszymi wersjami, jeśli nadal istnieją.

AsymLabs
źródło
1

Ponieważ moja praca jest używana przez osoby z Linuksem innym niż BSD oraz macOS, zdecydowałem się na użycie tych aliasów w naszych skryptach kompilacji ( seddołączonych, ponieważ ma podobne problemy):

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

Opcjonalne sprawdzenie, które możesz dodać, aby automatycznie zainstalować zależności 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

Przypuszczam, że jest naprawdę „globalny”, musi sprawdzać innych ... ale to prawdopodobnie zbliża się do 80/20.

clounie
źródło
1

Wyjaśnienie

coreutils to brewpakiet instalujący podstawowe narzędzia GNU / Linux, które odpowiadają ich implementacji w systemie Mac OSX, dzięki czemu można z nich korzystać

Możesz znaleźć programy lub narzędzia w systemie Mac OSX, które wydają się podobne do coreutils Linuxa („Core Utilities”), ale różnią się pod pewnymi względami (np. Mają różne flagi).

Wynika to z faktu, że implementacja tych narzędzi w systemie Mac OSX jest inna. Aby uzyskać oryginalne zachowanie podobne do GNU / Linux, możesz zainstalować coreutilspakiet za pośrednictwem brewsystemu zarządzania pakietami.

Spowoduje to zainstalowanie odpowiednich podstawowych narzędzi z prefiksem g. Na przykład readlinkznajdziesz odpowiedni greadlinkprogram.

Aby uzyskać readlinkwydajność podobną do implementacji GNU readlink( greadlink), możesz utworzyć prosty alias po zainstalowaniu coreutils.

Realizacja

  1. Zainstaluj napar

Postępuj zgodnie z instrukcjami na https://brew.sh/

  1. Zainstaluj pakiet coreutils

brew install coreutils

  1. Utwórz alias

Możesz umieścić swój alias w ~ / .bashrc, ~ / .bash_profile lub gdziekolwiek jesteś przyzwyczajony do przechowywania aliasów bash. Osobiście trzymam mój w ~ / .bashrc

alias readlink=greadlink

Możesz utworzyć podobne aliasy dla innych coreutils, takich jak gmv, gdu, gdf i tak dalej. Ale uwaga: zachowanie GNU na komputerze Mac może być mylące dla innych osób przyzwyczajonych do pracy z natywnymi rdzeniami lub może zachowywać się w nieoczekiwany sposób w systemie Mac.

Yosefrow
źródło
0

Napisałem narzędzie realpath dla OS X, które może zapewnić takie same wyniki jak readlink -f.


Oto przykład:

(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


Jeśli używasz MacPorts, możesz zainstalować go za pomocą następującego polecenia: sudo port selfupdate && sudo port install realpath.

użytkownik454322
źródło
0

Naprawdę niezależny od platformy byłby również ten R-onliner

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

Aby faktycznie naśladować readlink -f <path>, należy użyć 2 $ zamiast 1 $.

Holger Brandl
źródło
Tyle że R domyślnie prawie nie występuje w żadnym systemie. Jeszcze nie na Macu (od 10.15), o to właśnie chodzi w tym pytaniu.
Codesniffer
0

readlink -fImplementacja zgodna z POSIX dla skryptów powłoki POSIX

https://github.com/ko1nksm/readlinkf

Jest to zgodne z POSIX (bez bashizmu). Nie używa readlinkani realpath. Sprawdziłem , że jest dokładnie tak samo, porównując z GNU readlink -f(patrz wyniki testu ). Ma obsługę błędów i dobrą wydajność. Możesz bezpiecznie wymienić z readlink -f. Licencja jest CC0, więc możesz używać jej do każdego projektu.

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

Proszę odnieść się do najnowszego kodu. Może to naprawić.

Koichi Nakashima
źródło
-2

Perl ma funkcję readlink (np. Jak skopiować dowiązania symboliczne w Perlu? ). Działa to na większości platform, w tym OS X:

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

Na przykład:

$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c
jonjlee
źródło
5
Robi to tak samo, jak readlinkbez -fopcji - bez rekurencji, bez ścieżki bezwzględnej - więc równie dobrze możesz użyć readlinkbezpośrednio.
mklement0
-2

Odpowiedź @Keith Smith daje nieskończoną pętlę.

Oto moja odpowiedź, której używam tylko na SunOS (SunOS tak bardzo brakuje poleceń POSIX i GNU).

Jest to plik skryptu, który musisz umieścić w jednym z katalogów $ 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"
padlinożerca
źródło
-2

Oto, czego używam:

stat -f %N $your_path

Carlos Nunez
źródło
To nie drukuje pełnej ścieżki.
Thom Wiggers
-5

Ścieżki do readlink są różne w moim systemie i twoim. Spróbuj podać pełną ścieżkę:

/sw/sbin/readlink -f

ennuikiller
źródło
1
A jaka - dokładnie - jest różnica między / sw / sbin i / usr / bin? A dlaczego te dwa pliki binarne różnią się?
troelskn
4
Aha .. więc fink zawiera zamiennik, readlinkktóry jest kompatybilny z GNU. Miło to wiedzieć, ale to nie rozwiązuje mojego problemu, ponieważ potrzebuję skryptu, aby działał na maszynie innych ludzi i nie mogę wymagać od nich zainstalowania fink.
troelskn
Nie odpowiadasz na pierwotne pytanie. Proszę przepisać swoją odpowiedź.
Jaime Hablutzel