Come posso ottenere il comportamento del readlink di GNU -f su un Mac?
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 ...]
Soluzione
readlink -f
fa due cose:
- Esegue una sequenza di collegamenti simbolici fino a quando non trova un file effettivo.
- 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.
- Installa homebrew
- Esegui " brew install coreutils "
- 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
- Installa brew
Segui le istruzioni su https://brew.sh/
- Installa il pacchetto coreutils
<=>
- 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"