Domanda

Su Linux, l'utilità readlink accetta un'opzione -f che segue collegamenti aggiuntivi. Questo non sembra funzionare su Mac e probabilmente su sistemi basati su BSD. Quale sarebbe l'equivalente?

Ecco alcune informazioni di debug:

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

Soluzione

readlink -f fa due cose:

  1. Esegue una sequenza di collegamenti simbolici fino a quando non trova un file effettivo.
  2. Restituisce il nome canonico # 8212 di quel file; cioè, il suo nome di percorso assoluto.

Se vuoi, puoi semplicemente creare uno script di shell che usi il comportamento vanlink readlink per ottenere la stessa cosa. Ecco un esempio Ovviamente potresti inserirlo nel tuo script dove desideri chiamare pwd -P

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

Nota che questo non include alcuna gestione degli errori. Di particolare importanza, non rileva i cicli di collegamento simbolico. Un modo semplice per farlo sarebbe quello di contare il numero di volte che vai in giro e fallisci se colpisci un numero improbabilmente grande, come 1.000.

MODIFICATO per utilizzare $PWD anziché ./script_name filename.

Nota che questo script prevede di essere chiamato come -f, no $1, cambia $2 in -f filename se vuoi essere in grado di usare con <=> come GNU readlink.

Altri suggerimenti

MacPorts e Homebrew forniscono un pacchetto coreutils contenente greadlink (GNU readlink). Ringraziamo il post di Michael Kallweitt su mackb.com.

brew install coreutils

greadlink -f file.txt

Potresti essere interessato a realpath(3) , o os.path.realpath . I due non sono esattamente gli stessi; la chiamata alla libreria C richiede l'esistenza di componenti del percorso intermedio, mentre la versione di Python no.

$ 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

So che hai detto che preferiresti qualcosa di più leggero di un altro linguaggio di scripting, ma nel caso in cui compilare un binario sia insopportabile, puoi usare Python e ctypes (disponibili su Mac OS X 10.5) per concludere la chiamata in libreria:

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

Ironia della sorte, la versione C di questo script dovrebbe essere più breve. :)

Odio continuare con un'altra implementazione, ma avevo bisogno di a) un'implementazione portatile, pure shell eb) copertura unit-test , come il numero di casi limite per qualcosa di simile sono non banali .

Vedi il mio progetto su Github per test e codice completo. Quella che segue è una sinossi dell'implementazione:

Come sottolinea astutamente Keith Smith, readlink -f fa due cose: 1) risolve ricorsivamente i link simbolici e 2) canonicalizza il risultato, quindi:

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

Innanzitutto, l'implementazione del resolver 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 
}

Nota che questa è una versione leggermente semplificata di l'implementazione completa . L'implementazione completa aggiunge un piccolo controllo per i cicli di collegamento simbolico , oltre a massaggiare un po 'l'output.

Infine, la funzione per canonicalizzare un percorso:

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

Questo è tutto, più o meno. Abbastanza semplice da incollare nel tuo script, ma abbastanza complicato da farti impazzire per fare affidamento su qualsiasi codice che non ha unit test per i tuoi casi d'uso.

  1. Installa homebrew
  2. Esegui " brew install coreutils "
  3. Esegui " greadlink -f path "

greadlink è il readlink gnu che implementa -f. Puoi usare macport o anche altri, preferisco l'homebrew.

Un semplice one-liner in perl che sicuramente funzionerà quasi ovunque senza dipendenze esterne:

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

Simboli simbolici di dereference.

L'uso in uno script potrebbe essere così:

readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"

Ho realizzato personalmente uno script chiamato realpath che assomiglia un po 'a:

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

Che dire di questo?

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

FreeBSD e OSX hanno una versione di stat derivata da NetBSD.

È possibile regolare l'output con i selettori di formato (vedere le pagine del manuale ai collegamenti sopra).

%  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

Alcune versioni di OS X di -f%R potrebbero non disporre dell'opzione -stat -f%Y per i formati. In questo caso -f%Y potrebbe essere sufficiente. L'opzione perl mostra la destinazione di un collegamento simbolico, mentre <=> mostra il percorso assoluto corrispondente al file.

Modifica

Se sei in grado di utilizzare Perl (Darwin / OS X viene installato con le versioni recenti di <=>), allora:

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

funzionerà.

Ecco una funzione di shell portatile che dovrebbe funzionare in QUALSIASI shell paragonabile di Bourne. Risolverà la punteggiatura relativa del percorso & Quot; .. o. & Quot; e dereference link simbolici.

Se per qualche motivo non si dispone di un comando realpath (1) o readlink (1), questo può essere aliasato.

which realpath || alias realpath='real_path'

Enjoy:

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

inoltre, nel caso in cui qualcuno sia interessato qui è come implementare basename e dirname in un codice shell puro al 100%:

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

Puoi trovare la versione aggiornata di questo codice shell sul mio sito google: http: // siti. google.com/site/jdisnard/realpath

EDIT: Questo codice è concesso in licenza in base ai termini della licenza di 2 clausole (stile freeBSD). Una copia della licenza può essere trovata seguendo il collegamento ipertestuale sopra al mio sito.

Il modo più semplice per risolvere questo problema e abilitare la funzionalità di readlink su Mac con Homebrew installato o FreeBSD è installare il pacchetto 'coreutils'. Potrebbe essere necessario anche su alcune distribuzioni Linux e altri sistemi operativi POSIX.

Ad esempio, in FreeBSD 11, ho installato invocando:

# pkg install coreutils

Su MacOS con Homebrew, il comando sarebbe:

$ brew install coreutils

Non sono proprio sicuro del perché le altre risposte siano così complicate, questo è tutto. I file non si trovano in un posto diverso, non sono ancora installati.

Inizia aggiornamento

Questo è un problema così frequente che abbiamo messo insieme una libreria di Bash 4 per uso gratuito (Licenza MIT) chiamata realpath-lib . Questo è progettato per emulare readlink -f per impostazione predefinita e include due suite di test per verificare (1) che funzioni per un determinato sistema unix e (2) contro readlink -f se installato (ma questo non è richiesto). Inoltre, può essere utilizzato per indagare, identificare e svolgere collegamenti simbolici profondi e rotti e riferimenti circolari, quindi può essere uno strumento utile per diagnosticare problemi di directory e file fisici o simbolici profondamente annidati. È disponibile all'indirizzo github.com o bitbucket.org .

Fine aggiornamento

Un'altra soluzione molto compatta ed efficiente che non fa affidamento su nient'altro che Bash è:

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

}

Ciò include anche un'impostazione ambientale no_symlinks che consente di risolvere i collegamenti simbolici al sistema fisico. Finché no_symlinks='on' è impostato su qualcosa, ovvero <=>, i collegamenti simbolici verranno risolti sul sistema fisico. Altrimenti verranno applicati (impostazione predefinita).

Questo dovrebbe funzionare su qualsiasi sistema che fornisce Bash e restituirà un codice di uscita compatibile con Bash a scopo di test.

Ci sono già molte risposte, ma nessuna ha funzionato per me ... Quindi questo è quello che sto usando ora.

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

Meglio tardi che mai, suppongo. Ero motivato a svilupparlo specificamente perché i miei script Fedora non funzionavano sul Mac. Il problema è dipendenze e Bash. I Mac non li hanno, o se lo fanno, sono spesso da qualche altra parte (un altro percorso). La manipolazione del percorso di dipendenza in uno script Bash multipiattaforma nella migliore delle ipotesi è un mal di testa e un rischio per la sicurezza nella peggiore delle ipotesi, quindi è meglio evitarne l'uso, se possibile.

La seguente funzione get_realpath () è semplice, incentrata su Bash e non sono richieste dipendenze. Uso solo i builtin di Bash echo e cd . È anche abbastanza sicuro, poiché tutto viene testato in ogni fase del percorso e restituisce condizioni di errore.

Se non vuoi seguire i collegamenti simbolici, metti imposta -P nella parte anteriore dello script, ma in caso contrario cd dovrebbe risolvere i collegamenti simbolici per impostazione predefinita. È stato testato con argomenti sui file che sono {absolute | parente | symlink | local} e restituisce il percorso assoluto al file. Finora non abbiamo avuto alcun problema.

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

}

Puoi combinarlo con altre funzioni get_dirname, get_filename, get_stemname e validate_path. Questi possono essere trovati nel nostro repository GitHub come realpath-lib (informativa completa - questo è il nostro prodotto ma lo offriamo gratuitamente alla comunità senza alcuna restrizione). Potrebbe anche servire come strumento didattico - è ben documentato.

Abbiamo fatto del nostro meglio per applicare le cosiddette pratiche "moderne di Bash", ma Bash è un argomento importante e sono certo che ci sarà sempre spazio per miglioramenti. Richiede Bash 4+ ma potrebbe funzionare con versioni precedenti se sono ancora presenti.

Veramente indipendente dalla piattaforma sarebbe anche questo R-onliner

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

Per imitare effettivamente readlink -f <path>, dovrebbero essere usati $ 2 anziché $ 1.

Poiché il mio lavoro è utilizzato da persone con Linux non BSD e macOS, ho optato per l'utilizzo di questi alias nei nostri script di build (sed incluso poiché presenta problemi simili):

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

Controllo facoltativo che è possibile aggiungere per installare automaticamente homebrew + coreutils dipendenze:

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

Suppongo che sia veramente " globale " deve controllare gli altri ... ma questo probabilmente si avvicina al segno 80/20.

Ho scritto un'utilità realpath per OS X che può fornire gli stessi risultati di readlink -f.

Ecco un esempio:

(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

Se stai usando MacPorts, puoi installarlo con il seguente comando: sudo port selfupdate && sudo port install realpath.

Spiegazione

coreutils è un pacchetto brew che installa utility di base GNU / Linux che corrispondono all'implementazione di Mac OSX in modo da poterle usare

Potresti trovare programmi o programmi di utilità sul tuo sistema mac osx che sembrano simili ai coreutils di Linux (" Core Utilities ") ma differiscono in alcuni modi (come avere flag diversi).

Questo perché l'implementazione di Mac OSX di questi strumenti è diversa. Per ottenere il comportamento originale simile a GNU / Linux è possibile installare il pacchetto coreutils tramite il sistema di gestione dei pacchetti g.

In questo modo verranno installate le utility core corrispondenti, precedute da readlink. Per esempio. per greadlink, troverai un brew install coreutils programma corrispondente.

Per fare in modo che alias readlink=greadlink funzioni come l'implementazione GNU <=> (<=>), puoi creare un semplice alias dopo aver installato coreutils.

Implementazione

  1. Installa brew

Segui le istruzioni su https://brew.sh/

  1. Installa il pacchetto coreutils

<=>

  1. Crea un alias

Puoi posizionare il tuo alias in ~ / .bashrc, ~ / .bash_profile o ovunque tu sia abituato a mantenere i tuoi alias bash. Personalmente tengo il mio in ~ / .bashrc

<=>

Puoi creare alias simili per altri coreutil come gmv, gdu, gdf e così via. Ma fai attenzione che il comportamento GNU su una macchina mac potrebbe essere fonte di confusione per gli altri abituati a lavorare con coreutils nativi o potrebbe comportarsi in modo imprevisto sul tuo sistema mac.

Questo è quello che uso:

stat -f %N $your_path

I percorsi per readlink sono diversi tra il mio sistema e il tuo. Prova a specificare il percorso completo:

  

/sw/sbin/readlink -f

Perl ha una funzione readlink (ad es. Come si copiano i collegamenti simbolici in Perl? ). Funziona su quasi tutte le piattaforme, incluso OS X:

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

Ad esempio:

$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c

La risposta di @Keith Smith fornisce un ciclo infinito.

Ecco la mia risposta, che uso solo su SunOS (a SunOS mancano così tanti comandi POSIX e GNU).

È un file di script che devi inserire in una delle tue directory $ 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"
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top