Come risolvere i collegamenti simbolici in uno script di shell


220

Dato un percorso assoluto o relativo (in un sistema simile a Unix), vorrei determinare il percorso completo del target dopo aver risolto eventuali collegamenti simbolici intermedi. Punti bonus per la risoluzione di ~ notazione del nome utente contemporaneamente.

Se la destinazione è una directory, potrebbe essere possibile chdir () nella directory e quindi chiamare getcwd (), ma voglio davvero farlo da uno script di shell piuttosto che scrivere un helper C. Sfortunatamente, le shell hanno la tendenza a provare a nascondere l'esistenza di collegamenti simbolici all'utente (questo è bash su OS X):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

Quello che voglio è una funzione resol () tale che quando eseguito dalla directory tmp nell'esempio sopra, risolve ("pippo") == "/ Users / greg / tmp / bar".

Risposte:


92

Secondo gli standard, pwd -P dovrebbe restituire il percorso con i collegamenti simbolici risolti.

Funzione C char *getcwd(char *buf, size_t size)daunistd.h dovrebbe avere lo stesso comportamento.

getcwd pwd


22
Questo funziona solo per (l'attuale) directory? Se il bersaglio è un file, non darà nulla ...
dala,

4
Non funziona se il collegamento viene interrotto in quanto non è possibile renderlo il percorso corrente
Tom Howard,

6
Riassumendo con il senno di poi: questa risposta funziona solo in circostanze molto limitate, vale a dire se il collegamento simbolico di interesse è con una directory che esiste realmente ; inoltre, devi cdprima farlo, prima di chiamare pwd -P. In altre parole: non vi permetterà di risolvere (vedi il bersaglio di) collegamenti simbolici ai file o di rotte link simbolici, e per la risoluzione dei link simbolici-directory esistente che devi fare il lavoro supplementare (ripristinare il precedente dir di lavoro o di localizzare l' cde pwd -Pchiamate in una subshell).
mklement0

a male cerco un modo per risolvere un file e non una directory.
Martin

Come altri hanno sottolineato, questo in realtà non risponde alla domanda. La risposta di @ pixelbeat qui sotto lo fa.
Sostituisce il

401
readlink -f "$path"

Nota del redattore: quanto sopra funziona con GNU readlink e FreeBSD / PC-BSD / OpenBSD readlink , ma non su OS X dal 10.11.
GNU readlink offre ulteriori opzioni correlate, ad esempio -mper risolvere un collegamento simbolico indipendentemente dal fatto che esista o meno l'obiettivo finale.

Da GNU coreutils 8.15 (2012-01-06), è disponibile un programma realpath meno ottuso e più flessibile di quanto sopra. È anche compatibile con l'utility FreeBSD con lo stesso nome. Include anche funzionalità per generare un percorso relativo tra due file.

realpath $path

[Aggiunta dell'amministratore di seguito dal commento di halloleo - danorton]

Per Mac OS X (almeno con il 10.11.x), utilizzare readlinksenza l' -fopzione:

readlink $path

Nota dell'editore: Questo non risolverà ricorsivamente i link simbolici e quindi non riporterà l' obiettivo finale ; ad es., dato il collegamento simbolico a acui punta b, che a sua volta indica c, questo riporterà solo b(e non garantirà che venga emesso come percorso assoluto ).
Utilizzare il seguente perlcomando su OS X per riempire il vuoto della readlink -ffunzionalità mancante :
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"


5
Questo non funziona in Mac OS X - vedere stackoverflow.com/questions/1055671/...
Bkkbrad

1
peccato per l'incompatibilità di OS X, altrimenti bello +1
jkp

11
Su OS X puoi installare coreutils con homebrew. Lo installa come "grealpath".
Kief,

10
readlinkfunziona su OSX, ma necessita di un'altra sintassi: readlink $path senza il -f.
halloleo,

2
readlink non riesce a de-referenziare più livelli di symlink, ma derefa solo uno strato alla volta
Magnus,

26

"pwd -P" sembra funzionare se si desidera solo la directory, ma se per qualche motivo si desidera il nome dell'eseguibile effettivo, non credo che sia di aiuto. Ecco la mia soluzione:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

17

Uno dei miei preferiti è realpath foo

realpath - restituisce il percorso assoluto canonicalizzato

realpath espande tutti i collegamenti simbolici e risolve i riferimenti ai caratteri '/./', '/../' ed extra '/' nella stringa terminata nulla denominata da path e
       memorizza il percorso assoluto canonicalizzato nel buffer di dimensioni PATH_MAX denominato da resolved_path. Il percorso risultante non avrà link simbolici, '/./' o
       componenti '/../'.

Su Debian (etch e successivi) questo comando è disponibile nel pacchetto realpath.
Phil Ross,

2
realpath è ora (gennaio 2012) parte di coreutils e retrocompatibile con le varianti debian e BSD
pixelbeat

1
Non ho realpathsu Centos 6 con GNU coreutils 8.4.31. Ne ho incontrati molti altri su Unix e Linux che hanno un coreutils GNU impacchettato senza realpath . Quindi sembra dipendere da qualcosa di più della semplice versione.
Toxalot,

Preferisco realpathrispetto readlinkperché offre flag di opzione come--relative-to
arr_sea

10
readlink -e [filepath]

sembra essere esattamente quello che stai chiedendo - accetta un percorso arbirario, risolve tutti i collegamenti simbolici e restituisce il percorso "reale" - ed è "standard * nix" che probabilmente tutti i sistemi hanno già


Mi ha appena morso. Non funziona su Mac e sto cercando un sostituto.
discesa

5

Un altro modo:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

Ho bisogno di cd in myreadlink (), perché è una funzione ricorsiva, andando in ogni directory fino a trovare un collegamento. Se trova un collegamento, restituisce il percorso reale, quindi sed sostituirà il percorso.
Keymon,

5

Mettendo insieme alcune delle soluzioni fornite, sapendo che readlink è disponibile sulla maggior parte dei sistemi, ma ha bisogno di argomenti diversi, questo funziona bene per me su OSX e Debian. Non sono sicuro dei sistemi BSD. Forse la condizione deve essere quella [[ $OSTYPE != darwin* ]]di escludere -fsolo da OSX.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"

3

Ecco come si può ottenere il percorso effettivo del file in MacOS / Unix usando uno script Perl in linea:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

Allo stesso modo, per ottenere la directory di un file con collegamento simbolico:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

3

Il tuo percorso è una directory o potrebbe essere un file? Se è una directory, è semplice:

(cd "$DIR"; pwd -P)

Tuttavia, se potrebbe essere un file, questo non funzionerà:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

perché il collegamento simbolico potrebbe risolversi in un percorso relativo o completo.

Sugli script devo trovare il percorso reale, in modo da poter fare riferimento alla configurazione o ad altri script installati insieme ad esso, utilizzo questo:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

È possibile impostare SOURCEqualsiasi percorso di file. Fondamentalmente, fintanto che il percorso è un collegamento simbolico, risolve quel collegamento simbolico. Il trucco è nell'ultima riga del ciclo. Se il collegamento simbolico risolto è assoluto, lo utilizzerà come SOURCE. Tuttavia, se è relativo, lo anticiperà DIR, che è stato risolto in una posizione reale dal semplice trucco che ho descritto per la prima volta.


2
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..

Ciò si interromperà con (a) qualsiasi percorso contenente spazi vuoti o metacaratteri di shell e (b) collegamenti simbolici interrotti (non un problema se si desidera semplicemente il percorso principale dello script in esecuzione, supponendo che (a) non si applichi).
mklement0

Se sei interessato agli spazi bianchi usa le virgolette.
Dave,

Si prega di fare - anche se questo non risolverà (b).
mklement0

Per favore mostrami un esempio di b) dove questo fallisce. Per definizione, un collegamento simbolico interrotto punta a una voce della directory che non esiste. Lo scopo di questo script è risolvere i collegamenti simbolici nell'altra direzione. Se il collegamento simbolico fosse interrotto non eseguiresti lo script. Questo esempio ha lo scopo di dimostrare la risoluzione dello script attualmente in esecuzione.
Dave,

"intendeva dimostrare la risoluzione della sceneggiatura attualmente in esecuzione" - in effetti, che è una limitazione del campo di applicazione della domanda su cui hai deciso di concentrarti; va benissimo, purché tu lo dica. Dal momento che non l'hai fatto, l'ho dichiarato nel mio commento. Correggi il problema di quotazione, che è un problema a prescindere dall'ambito della risposta.
mklement0

2

Nota: credo che questa sia una soluzione solida, portatile e pronta all'uso, che è invariabilmente lunga per questo motivo.

Di seguito è riportato uno script / funzione completamente conforme a POSIX che è quindi multipiattaforma (funziona anche su macOS, il cui readlinkancora non supporta a -fpartire da 10.12 (Sierra)) - utilizza solo le funzionalità del linguaggio shell POSIX e solo chiamate di utilità conformi a POSIX .

È un'implementazione portatile di GNUreadlink -e (la versione più rigorosa di readlink -f).

È possibile eseguire lo script di consh o fonte la funzione di a bash, kshezsh :

Ad esempio, all'interno di uno script è possibile utilizzarlo come segue per ottenere la vera directory di origine dello script in esecuzione, con i collegamenti simbolici risolti:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink definizione script / funzione:

Il codice è stato adattato con gratitudine da questa risposta .
Ho anche creato una bashversione dell'utilità stand-alone basata qui , che puoi installare
npm install rreadlink -gse hai installato Node.js.

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

Una tangente alla sicurezza:

jarno , in riferimento alla funzione che assicura che builtin commandnon sia ombreggiato da una funzione alias o shell con lo stesso nome, chiede in un commento:

Cosa succede se unaliaso unsete [sono impostati come alias o funzioni shell?

La motivazione dietro a rreadlinkgarantire che commandabbia il suo significato originale è di usarlo per bypassare gli alias e le funzioni di convenienza (benigni) spesso usati per oscurare i comandi standard nelle shell interattive, come ridefinire lsper includere le opzioni preferite.

Penso che sia giusto dire che a meno che non hai a che fare con un ambiente non attendibile, dannoso, preoccuparsi unaliaso unset- o, se è per questo, while, do, ... - ridefinizione non è una preoccupazione.

C'è qualcosa su cui la funzione deve fare affidamento per avere il suo significato e comportamento originali - non c'è modo di aggirarlo.
Che shell simili a POSIX consentano la ridefinizione dei builtin e persino delle parole chiave del linguaggio è intrinsecamente un rischio per la sicurezza (e scrivere codice paranoico è difficile in generale).

Per rispondere in modo specifico alle tue preoccupazioni:

La funzione si basa unaliase unsetha il suo significato originale. Farli ridefinire come funzioni shell in un modo che altera il loro comportamento sarebbe un problema; la ridefinizione come alias non è necessariamente un problema, poiché la citazione (parte del) nome del comando (ad es. \unalias) ignora gli alias.

Tuttavia, citando è non un'opzione per shell parole chiave ( while, for, if, do, ...) e mentre le parole chiave shell prevalgono sulle shell funzioni , in bashe zshgli alias hanno la più alta precedenza, in modo da guardia contro ridefinizioni shell-parola chiave è necessario correre unaliascon i loro nomi (anche se nelle shell non interattive bash (come gli script) gli alias non sono espansi per impostazione predefinita - solo se shopt -s expand_aliasesesplicitamente chiamati per primi).

Per assicurarti che unalias- come incorporato - abbia il suo significato originale, devi prima usarlo \unset, il che richiede che unsetabbia il suo significato originale:

unsetè una shell incorporata , quindi per assicurarti che sia invocata come tale, dovresti assicurarti che non venga ridefinita come funzione . Sebbene sia possibile bypassare un modulo alias con la quotazione, non è possibile ignorare un modulo con funzione shell: prendere 22.

Pertanto, a meno che non si possa fare affidamento sul unsetsignificato originale, da quello che posso dire, non esiste un modo garantito per difendersi da tutte le ridefinizioni dannose.


Nella mia esperienza, la citazione esclude gli alias, non le funzioni della shell, a differenza di quanto tu abbia detto per la prima volta.
jarno

Ho provato che posso definire [come un alias in dashe bashe come una funzione di shell in bash.
jarno

1
Ho espresso il mio punto come domanda separata .
jarno

@jarno: punti positivi; Ho aggiornato la mia risposta; fammi sapere se pensi che ci sia ancora un problema.
mklement0

1
@jarno: puoi definire whileuna funzione in bash, kshe zsh(ma non dash), ma solo con la function <name>sintassi: function while { echo foo; }funziona ( while() { echo foo; }non funziona). Tuttavia, ciò non oscurerà la while parola chiave , poiché le parole chiave hanno una precedenza maggiore rispetto alle funzioni (l'unico modo per invocare questa funzione è come \while). In bashe zsh, gli alias hanno una precedenza maggiore rispetto alle parole chiave, quindi le alias ridefinizioni delle parole chiave le ombreggiano (ma bashper impostazione predefinita solo nelle shell interattive , a meno che non shopt -s expand_aliasessia esplicitamente chiamato).
mklement0

1

Gli script di shell comuni devono spesso trovare la loro directory "home" anche se vengono invocati come collegamento simbolico. Lo script deve quindi trovare la sua posizione "reale" da soli $ 0.

cat `mvn`

sul mio sistema stampa uno script contenente quanto segue, che dovrebbe essere un buon suggerimento per ciò di cui hai bisogno.

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`

1

Prova questo:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

1

Da quando l'ho incontrato molte volte nel corso degli anni, e questa volta avevo bisogno di una versione portatile bash pura che potevo usare su OSX e Linux, sono andato avanti e ne ho scritto uno:

La versione vivente vive qui:

https://github.com/keen99/shell-functions/tree/master/resolve_path

ma per il bene di SO, ecco la versione corrente (sento che è ben testato ... ma sono aperto al feedback!)

Potrebbe non essere difficile farlo funzionare per la semplice bourne shell (sh), ma non ci ho provato ... Mi piace troppo $ FUNCNAME. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

ecco un classico esempio, grazie a brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

usa questa funzione e restituirà il percorso -real-:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 

La mia risposta precedente fa esattamente la stessa cosa in circa 1/4 dello spazio :) È piuttosto uno script di shell di base e non degno di un repository git.
Dave,

1

Per ovviare all'incompatibilità del Mac, mi è venuta in mente

echo `php -r "echo realpath('foo');"`

Non eccezionale ma cross OS


3
Python 2.6+ è disponibile su più sistemi di utenti finali rispetto a php, quindi python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"probabilmente avrebbe più senso. Tuttavia, quando devi usare Python da uno script di shell, probabilmente dovresti semplicemente usare Python e risparmiarti il ​​mal di testa degli script di shell multipiattaforma.
Jonathan Baldwin,

Non hai bisogno di altro che il link di lettura incorporato sh, dirname e basename.
Dave,

@ Dave: dirname, basenamee readlinksono esterni utenze , non shell built-in; dirnamee basenamefanno parte di POSIX, readlinknon lo è.
mklement0

@ mklement0 - Hai ragione. Sono forniti da CoreUtils o equivalenti. Non dovrei visitare SO dopo l'1. L'essenza del mio commento è che non sono richiesti né PHP né altri interpreti di linguaggio oltre a quelli installati in un sistema di base. Ho usato lo script che ho fornito in questa pagina su ogni variante di Linux dal 1997 e MacOS X dal 2006 senza errori. L'OP non ha richiesto una soluzione POSIX. Il loro ambiente specifico era Mac OS X.
Dave,

@Dave: Sì, è possibile farlo con programmi di utilità stock, ma è anche difficile farlo (come evidenziato dalle carenze della tua sceneggiatura). Se OS X fosse veramente al centro dell'attenzione, allora questa risposta sarebbe perfettamente soddisfacente - e molto più semplice - dato che phparriva con OS X. Tuttavia, anche se il corpo della domanda menziona OS X, non è taggato come tale ed è diventato chiaro che le persone su varie piattaforme vengono qui per le risposte, quindi vale la pena sottolineare cosa è specifico della piattaforma / non POSIX.
mklement0

1

Questo è un risolutore di collegamenti simbolici in Bash che funziona se il collegamento è una directory o una non directory:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

Si noti che tutte le cose cde setsi svolgono in una subshell.


In realtà, il {} attorno a () non è necessario, poiché () conta come un "comando composto", proprio come {}. Ma hai ancora bisogno () dopo il nome della funzione.
Chris Cogdon,

@ChrisCogdon I {}dintorni di ()non sono inutili se non ci sono ()dietro il nome della funzione. Bash accetta le dichiarazioni di funzioni senza ()perché la shell non ha elenchi di parametri nelle dichiarazioni e non fa chiamate con le ()dichiarazioni di funzioni con ()non ha molto senso.
solidsnack

0

Qui presento quella che credo sia una soluzione multipiattaforma (almeno Linux e macOS) alla risposta che attualmente funziona bene per me.

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

Ecco una soluzione macOS (solo?). Forse più adatto alla domanda originale.

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}

0

La mia risposta qui Bash: come ottenere il vero percorso di un collegamento simbolico?

ma in breve molto utile negli script:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

Questi fanno parte dei coreutils GNU, adatti per l'uso in sistemi Linux.

Per testare tutto, mettiamo symlink in / home / test2 /, modificiamo alcune cose aggiuntive ed eseguiamo / chiamiamolo dalla directory root:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

Dove

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink

0

Nel caso in cui pwd non possa essere usato (es. Chiamando uno script da una posizione diversa), usa realpath (con o senza dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Funziona sia quando si chiama tramite (più) symlink (s) o quando si chiama direttamente lo script - da qualsiasi posizione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.