Domanda

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 aver risolto contemporaneamente anche la notazione ~ nome utente.

Se la destinazione è una directory, potrebbe essere possibile inserire chdir() nella directory e quindi chiamare getcwd(), ma in realtà voglio farlo da uno script di shell anziché scrivere un helper C.Sfortunatamente, le shell hanno la tendenza a cercare di nascondere all'utente l'esistenza dei collegamenti simbolici (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 risolve() tale che, quando eseguita dalla directory tmp nell'esempio precedente, risolve("foo") == "/Users/greg/tmp/bar".

È stato utile?

Soluzione

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

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

getcwd pwd

Altri suggerimenti

readlink -f "$path"

Nota dell'editore:Quanto sopra funziona con GNU readlink E FreeBSD/PC-BSD/OpenBSD readlink, Ma non su OS X a partire dal 10.11.
GNU readlink offre opzioni aggiuntive e correlate, come ad esempio -m per risolvere un collegamento simbolico indipendentemente dal fatto che esista o meno l'obiettivo finale.

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

realpath $path

[Aggiunta amministrativa di seguito dal commento di HalloleoDanorton]

Per Mac OS X (fino ad almeno 10.11.x), utilizzare readlink senza il -f opzione:

readlink $path

Nota dell'editore:Ciò non risolverà i collegamenti simbolici ricorsivamente e quindi non riporterò il file ultimo bersaglio;ad esempio, dato il collegamento simbolico a che punta a b, che a sua volta indica c, questo riporterà solo b (e non garantirà che venga visualizzato come file percorso assoluto).
Utilizza il seguente perl comando su OS X per colmare il vuoto dei mancanti readlink -f funzionalità:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

"pwd -P" sembra funzionare se vuoi solo la directory, ma se per qualche motivo vuoi il nome dell'eseguibile vero e proprio, non penso che sia d'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

Uno dei miei preferiti è realpath foo

realpath - return the canonicalized absolute pathname

realpath  expands  all  symbolic  links  and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and
       stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path.  The resulting path will have no symbolic link, '/./' or
       '/../' components.
readlink -e [filepath]

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

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

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

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

Nota:Credo che questa sia una soluzione solida, portatile e già pronta, che lo è invariabilmente lungo proprio per questo motivo.

Di seguito è riportato un script/funzioni completamente conformi a POSIX questo è quindi multipiattaforma (funziona anche su macOS, di chi readlink ancora non supporta -f a partire dal 10.12 (Sierra)) - utilizza solo Funzionalità del linguaggio della shell POSIX e solo chiamate di utilità conformi a POSIX.

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

Puoi corri il sceneggiatura con sh O procurati il funzione In bash, ksh, E zsh:

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

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

rreadlink definizione di script/funzione:

Il codice è stato adattato con gratitudine da questa risposta.
Ho anche creato un file bash-versione di utilità autonoma basata Qui, con cui puoi installarlo
npm install rreadlink -g, se 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 sulla sicurezza:

Jarno, in riferimento alla funzione che garantisce che builtin command non è oscurato da un alias o da una funzione di shell con lo stesso nome, chiede in un commento:

Cosa succede se unalias O unset E [ sono impostati come alias o funzioni di shell?

La motivazione dietro rreadlink assicurandolo command ha il suo significato originale è usarlo per bypassare (benigno) convenienza alias e funzioni spesso utilizzati per oscurare i comandi standard nelle shell interattive, come la ridefinizione ls per includere le opzioni preferite.

Penso che sia giusto dirlo, a meno che tu non abbia a che fare con un ambiente non affidabile e dannoso, di cui preoccuparti unalias O unset - o, del resto, while, do, ...- essere ridefiniti non è una preoccupazione.

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

Per rispondere specificamente alle tue preoccupazioni:

La funzione si basa su unalias E unset aventi il ​​loro significato originario.Avendoli ridefiniti come funzioni della shell in un modo che altera il loro comportamento sarebbe un problema;ridefinizione come un alias non è necessariamente una preoccupazione, perché citando (parte del) nome del comando (ad esempio, \unalias) ignora gli alias.

Tuttavia, citare lo è non un'opzione per la shell parole chiave (while, for, if, do, ...) e mentre le parole chiave della shell hanno la precedenza sulla shell funzioni, In bash E zsh gli alias hanno la massima precedenza, quindi per proteggersi dalle ridefinizioni delle parole chiave della shell è necessario eseguire unalias con i loro nomi (anche se in non interattivo bash sono gli alias delle shell (come gli script). non espanso per impostazione predefinita - solo se shopt -s expand_aliases viene esplicitamente chiamato per primo).

Per assicurare che unalias - come built-in - ha il suo significato originale, devi usarlo \unset prima su di esso, il che lo richiede unset hanno il suo significato originale:

unset è una conchiglia incorporato, quindi per assicurarti che venga invocato come tale, dovresti assicurarti che esso stesso non sia ridefinito come funzione.Mentre puoi bypassare un modulo alias con le virgolette, non puoi bypassare un modulo con funzione di shell - cattura 22.

Quindi, a meno che tu non possa fare affidamento unset per avere il suo significato originale, da quello che posso dire, non esiste un modo garantito per difendersi da tutte le ridefinizioni dannose.

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

cat `mvn`

sul mio sistema stampa uno script contenente quanto segue, che dovrebbe essere un buon suggerimento su 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`
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"))/..

Prova questo:

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

Dato che mi sono imbattuto in questo molte volte nel corso degli anni, e questa volta avevo bisogno di una versione portatile pura di bash che potessi 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 attuale (penso che sia ben testata... ma sono aperto al feedback!)

Potrebbe non essere difficile farlo funzionare con 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 -reale-:

%% 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 

Per aggirare l'incompatibilità con il Mac, ho ideato

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

Non eccezionale ma compatibile con tutti i sistemi operativi

Ecco come è possibile ottenere il percorso effettivo del file in MacOS/Unix utilizzando 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'))")

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

(cd "$DIR"; pwd -P)

Tuttavia, se potrebbe essere un file, allora 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 ho bisogno di 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

Potresti impostare SOURCE a qualsiasi percorso di file.Fondamentalmente, finché il percorso è un collegamento simbolico, risolve quel collegamento simbolico.Il trucco è nell'ultima riga del loop.Se il collegamento simbolico risolto è assoluto, lo utilizzerà come SOURCE.Tuttavia, se è relativo, anteporrà il DIR per esso, che è stato risolto in un luogo reale con il semplice trucco che ho descritto per primo.

Credo che questo sia il vero e definitivo "modo per risolvere un collegamento simbolico" sia che si tratti di una directory o di una non directory, usando Bash:

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

Tieni presente che tutto il cd E set le cose si svolgono in una subshell.

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
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top