So lösen Sie symbolische Links in einem Shell-Skript auf

220

Bei einem gegebenen absoluten oder relativen Pfad (in einem Unix-ähnlichen System) möchte ich den vollständigen Pfad des Ziels nach dem Auflösen von Zwischensymlinks bestimmen. Bonuspunkte für das gleichzeitige Auflösen der ~ Benutzernamen-Notation.

Wenn das Ziel ein Verzeichnis ist, ist es möglicherweise möglich, chdir () in das Verzeichnis zu übertragen und dann getcwd () aufzurufen, aber ich möchte dies wirklich über ein Shell-Skript tun, anstatt einen C-Helfer zu schreiben. Leider neigen Shells dazu, die Existenz von Symlinks vor dem Benutzer zu verbergen (dies ist ein Bash unter 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
$

Was ich möchte, ist eine Funktion auflösen (), so dass bei Ausführung aus dem Verzeichnis tmp im obigen Beispiel auflösen ("foo") == "/ Users / greg / tmp / bar".

Greg Hewgill
quelle

Antworten:

92

Gemäß den Standards pwd -Psollte der Pfad mit aufgelösten Symlinks zurückgegeben werden.

Die C-Funktion char *getcwd(char *buf, size_t size)von unistd.hsollte das gleiche Verhalten haben.

getcwd pwd

Kauppi
quelle
22
Dies funktioniert nur für (das aktuelle) Verzeichnis? Wenn das Ziel eine Datei ist, wird es nichts geben ...
Dala
4
Funktioniert nicht, wenn der Link unterbrochen ist, da Sie ihn nicht zu Ihrem aktuellen Pfad machen können
Tom Howard
6
Um es im Nachhinein zusammenzufassen: Diese Antwort funktioniert nur unter sehr begrenzten Umständen, nämlich wenn der interessierende Symlink zu einem tatsächlich vorhandenen Verzeichnis ist ; Außerdem müssen Sie cdes zuerst tun, bevor Sie anrufen pwd -P. Mit anderen Worten: es erlaubt Sie nicht zu lösen Symlinks (das Ziel sehen) , um Dateien oder gebrochen Symlinks, und für die Lösung bestehenden Verzeichnis Symlinks Sie zusätzliche Arbeit zu tun haben (das vorherige Arbeitsverzeichnis wiederherstellen oder die Lokalisierung cdund pwd -PAnrufe in einer Unterschale).
mklement0
Schade, ich suche nach einer Möglichkeit, eine Datei und nicht ein Verzeichnis aufzulösen.
Martin
Wie andere betont haben, beantwortet dies die Frage nicht wirklich. Die Antwort von @ pixelbeat unten tut es.
Erstaples
401
readlink -f "$path"

Anmerkung des Herausgebers: Das Obige funktioniert mit GNU readlink und FreeBSD / PC-BSD / OpenBSD readlink , jedoch nicht unter OS X ab 10.11.
GNU readlink bietet zusätzliche, verwandte Optionen, z. B. -mzum Auflösen eines Symlinks, unabhängig davon, ob das endgültige Ziel vorhanden ist oder nicht.

Beachten Sie, dass seit GNU coreutils 8.15 (2012-01-06) ein Realpath- Programm verfügbar ist, das weniger stumpf und flexibler als das oben genannte ist. Es ist auch kompatibel mit dem gleichnamigen FreeBSD-Dienstprogramm. Es enthält auch Funktionen zum Generieren eines relativen Pfads zwischen zwei Dateien.

realpath $path

[Admin-Zusatz unten aus Kommentar von halloleo - danorton]

Verwenden Sie für Mac OS X (bis mindestens 10.11.x) readlinkohne die -fOption:

readlink $path

Anmerkung des Herausgebers: Dadurch werden Symlinks nicht rekursiv aufgelöst und das endgültige Ziel wird nicht gemeldet . Wenn beispielsweise ein Symlink angegeben wird a, auf den verweist b, auf den wiederum verwiesen wird, cwird dies nur gemeldet b(und es wird nicht sichergestellt, dass es als absoluter Pfad ausgegeben wird ).
Verwenden Sie den folgenden perlBefehl unter OS X, um die Lücke der fehlenden readlink -fFunktionalität zu schließen:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

Pixelbeat
quelle
5
Dies funktioniert nicht unter Mac OS X - siehe stackoverflow.com/questions/1055671/…
Bkkbrad
1
Schade um OS X Inkompatibilität, sonst schön +1
jkp
11
Unter OS X können Sie Coreutils mit Homebrew installieren. Es installiert es als "grealpath".
Kief
10
readlinkfunktioniert unter OSX, benötigt aber eine andere Syntax: readlink $path ohne die -f.
Halloleo
2
readlink kann nicht mehrere Ebenen von Symlink de-referenzieren, sondern nur eine Ebene gleichzeitig derextieren
Magnus
26

"pwd -P" scheint zu funktionieren, wenn Sie nur das Verzeichnis wollen, aber wenn Sie aus irgendeinem Grund den Namen der tatsächlichen ausführbaren Datei wollen, denke ich nicht, dass das hilft. Hier ist meine Lösung:

#!/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
tlrobinson
quelle
17

Einer meiner Favoriten ist realpath foo

realpath - gibt den kanonisierten absoluten Pfadnamen zurück

realpath erweitert alle symbolischen Links und löst Verweise auf '/./', '/../' und zusätzliche '/' Zeichen in der nullterminierten Zeichenfolge auf, die durch path und benannt ist
       speichert den kanonisierten absoluten Pfadnamen in dem Puffer mit der Größe PATH_MAX, der durch aufgelöster Pfad benannt ist. Der resultierende Pfad hat keine symbolische Verknüpfung, '/./' oder
       '/../' Komponenten.
Gregory
quelle
Unter Debian (Etch und höher) ist dieser Befehl im Realpath-Paket verfügbar.
Phil Ross
2
Realpath ist jetzt (Januar 2012) Teil von Coreutils und abwärtskompatibel mit der Debian- und BSD-Variante
Pixelbeat
1
Ich habe nicht realpathauf Centos 6 mit GNU Coreutils 8.4.31. Ich bin unter Unix und Linux auf mehrere andere gestoßen , die einen GNU-Coreutils ohne gepackt haben realpath. Es scheint also von mehr als nur der Version abhängig zu sein.
Toxalot
Ich ziehe es realpathüber readlinkweil es Optionsflags bietet wie--relative-to
arr_sea
10
readlink -e [filepath]

scheint genau das zu sein, wonach Sie fragen - es akzeptiert einen willkürlichen Pfad, löst alle Symlinks auf und gibt den "echten" Pfad zurück - und es ist "Standard * nix", den wahrscheinlich alle Systeme bereits haben

Chuck Kollars
quelle
Ich bin gerade davon gebissen worden. Es funktioniert nicht auf dem Mac und ich suche nach Ersatz.
Dhill
5

Ein anderer Weg:

# 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|"; } 
Keymon
quelle
Ich benötige cd in myreadlink (), da es sich um eine rekursive Funktion handelt, die in jedes Verzeichnis wechselt, bis ein Link gefunden wird. Wenn ein Link gefunden wird, wird der Realpfad zurückgegeben, und dann würde sed den Pfad ersetzen.
Keymon
5

Wenn Sie einige der angegebenen Lösungen zusammenstellen und wissen, dass Readlink auf den meisten Systemen verfügbar ist, aber unterschiedliche Argumente benötigt, funktioniert dies unter OSX und Debian gut. Bei BSD-Systemen bin ich mir nicht sicher. Möglicherweise muss die Bedingung sein [[ $OSTYPE != darwin* ]], -fnur von OSX auszuschließen .

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

So können Sie den tatsächlichen Pfad zur Datei in MacOS / Unix mithilfe eines Inline-Perl-Skripts ermitteln:

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

So erhalten Sie das Verzeichnis einer verknüpften Datei:

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

Ist Ihr Pfad ein Verzeichnis oder eine Datei? Wenn es ein Verzeichnis ist, ist es einfach:

(cd "$DIR"; pwd -P)

Wenn es sich jedoch möglicherweise um eine Datei handelt, funktioniert dies nicht:

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

weil der Symlink möglicherweise in einen relativen oder vollständigen Pfad aufgelöst wird.

Bei Skripten muss ich den tatsächlichen Pfad finden, damit ich auf die Konfiguration oder andere zusammen damit installierte Skripte verweisen kann. Ich verwende Folgendes:

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

Sie können einen SOURCEbeliebigen Dateipfad festlegen . Solange der Pfad ein Symlink ist, wird dieser Symlink grundsätzlich aufgelöst. Der Trick befindet sich in der letzten Zeile der Schleife. Wenn der aufgelöste Symlink absolut ist, wird er als verwendet SOURCE. Wenn es jedoch relativ ist, wird das dafür vorangestellt DIR, das durch den einfachen Trick, den ich zuerst beschrieben habe, in einen realen Ort aufgelöst wurde.

Daniel C. Sobral
quelle
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"))/..
Dave
quelle
Dies wird mit (a) jedem Pfad, der Leerzeichen oder Shell-Metazeichen enthält, und (b) fehlerhaften Symlinks unterbrochen (kein Problem, wenn Sie nur den übergeordneten Pfad des laufenden Skripts möchten, vorausgesetzt, (a) trifft nicht zu).
mklement0
Wenn Sie sich mit Leerzeichen befassen, verwenden Sie Anführungszeichen.
Dave
Bitte tun Sie dies - obwohl dies nicht (b) betrifft.
mklement0
Bitte zeigen Sie mir ein Beispiel für b) wo dies fehlschlägt. Per Definition verweist ein unterbrochener Symlink auf einen Verzeichniseintrag, der nicht vorhanden ist. Der Zweck dieses Skripts besteht darin, Symlinks in die andere Richtung aufzulösen. Wenn der Symlink unterbrochen wäre, würden Sie das Skript nicht ausführen. Dieses Beispiel soll das Auflösen des aktuell ausgeführten Skripts demonstrieren.
Dave
"soll demonstrieren, wie das aktuell ausgeführte Skript gelöst wird" - in der Tat, was eine Einschränkung des Umfangs der Frage darstellt, auf den Sie sich konzentriert haben; das ist vollkommen in Ordnung, solange du es sagst. Da du es nicht getan hast, habe ich es in meinem Kommentar angegeben. Bitte beheben Sie das Problem mit den Zitaten, das unabhängig vom Umfang der Antwort ein Problem darstellt.
mklement0
2

Hinweis: Ich glaube, dass dies eine solide, tragbare und vorgefertigte Lösung ist, die aus genau diesem Grund immer langwierig ist .

Im Folgenden finden Sie ein vollständig POSIX-kompatibles Skript / eine Funktion , die plattformübergreifend ist (funktioniert auch unter macOS, das ab 10.12 (Sierra) readlinkimmer noch nicht unterstützt wird -f). Es werden nur POSIX-Shell-Sprachfunktionen und nur POSIX-kompatible Dienstprogrammaufrufe verwendet .

Es ist eine tragbare Implementierung von GNUsreadlink -e (der strengeren Version von readlink -f).

Sie können das ausführen Skript mitsh oder beziehen Sie die Funktion in bash, kshundzsh :

In einem Skript können Sie es beispielsweise wie folgt verwenden, um das wahre Ursprungsverzeichnis des laufenden Skripts mit aufgelösten Symlinks abzurufen:

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

rreadlink Skript- / Funktionsdefinition:

Der Code wurde mit Dankbarkeit aus dieser Antwort angepasst .
Ich habe auch eine erstelltes bash-basierte Stand-alone - Programm - Version hier , die Sie mit installieren können
npm install rreadlink -g, wenn Sie Node.js installiert.

#!/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 "$@"

Eine Tangente an die Sicherheit:

jarno fragt in Bezug auf die Funktion, die sicherstellt, dass Builtin commandnicht von einem gleichnamigen Alias ​​oder einer Shell-Funktion überschattet wird, in einem Kommentar:

Was ist, wenn unaliasoder unsetund [als Aliase oder Shell-Funktionen festgelegt sind?

Die Motivation, um rreadlinksicherzustellen, dass dies commandseine ursprüngliche Bedeutung hat, besteht darin, es zu verwenden, um (harmlose) Komfort- Aliase und -Funktionen zu umgehen, die häufig zum Schattieren von Standardbefehlen in interaktiven Shells verwendet werden, z. B. die Neudefinition ls, um bevorzugte Optionen einzuschließen.

Ich denke , es ist sicher zu sagen , dass es sei denn , Sie ist der Umgang mit einer nicht vertrauenswürdigen, bösartigen Umgebung, mich darum zu kümmern unaliasoder unset- oder, was das betrifft, while, do, ... - neu definiert wird kein Problem.

Es gibt etwas , auf das sich die Funktion verlassen muss, um ihre ursprüngliche Bedeutung und ihr Verhalten zu erhalten - daran führt kein Weg vorbei.
Dass POSIX-ähnliche Shells die Neudefinition von integrierten Funktionen und sogar von Sprachschlüsselwörtern ermöglichen, ist von Natur aus ein Sicherheitsrisiko (und das Schreiben von paranoidem Code ist im Allgemeinen schwierig).

Um Ihre Bedenken speziell anzusprechen:

Die Funktion beruht auf unaliasund unsethat ihre ursprüngliche Bedeutung. Es wäre ein Problem, wenn sie in einer Weise als Shell-Funktionen neu definiert würden, die ihr Verhalten verändert. Die Neudefinition als Alias ist nicht unbedingt ein Problem, da das Zitieren (eines Teils) des Befehlsnamens (z. B. \unalias) Aliase umgeht.

Jedoch unter Angabe ist nicht eine Option für die Shell - Schlüsselwörter ( while, for, if, do, ...) und während Shell keywords tun hat Vorrang vor Shell - Funktionen , in bashund zshAliase hat die höchste Priorität, so zum Schutz vor Shell-Schlüsselwort Neudefinitionen Sie ausführen müssen , unaliasmit Ihre Namen (obwohl in nicht interaktiven bash Shells (wie Skripten) Aliase standardmäßig nicht erweitert werden - nur wenn shopt -s expand_aliasessie zuerst explizit aufgerufen werden).

Um sicherzustellen, dass unalias- als eingebautes - seine ursprüngliche Bedeutung hat, müssen Sie \unsetes zuerst verwenden, was erfordert, dass unsetes seine ursprüngliche Bedeutung hat:

unsetist eine eingebaute Shell. Um sicherzustellen, dass sie als solche aufgerufen wird, müssen Sie sicherstellen, dass sie selbst nicht als Funktion neu definiert wird . Während Sie ein Alias-Formular mit Anführungszeichen umgehen können, können Sie ein Shell-Funktionsformular nicht umgehen - catch 22.

Daher unsetkann es keine garantierte Möglichkeit geben, sich gegen alle böswilligen Neudefinitionen zu verteidigen, es sei denn, Sie können sich darauf verlassen , dass sie ihre ursprüngliche Bedeutung haben.

mklement0
quelle
Nach meiner Erfahrung umgeht das Zitieren Aliase und nicht Shell-Funktionen, anders als Sie zuerst sagen.
Jarno
Ich habe getestet, dass ich [als Alias ​​in dashund bashund als Shell-Funktion in definieren kann bash.
Jarno
1
Ich habe meinen Standpunkt als separate Frage dargelegt .
Jarno
@jarno: Gute Punkte; Ich habe meine Antwort aktualisiert. Lassen Sie mich wissen, wenn Sie glauben, dass es immer noch ein Problem gibt.
mklement0
1
@jarno: Sie können definieren whileals eine Funktion in bash, kshund zsh(aber nicht dash), aber nur mit function <name>Syntax: function while { echo foo; }Werke ( while() { echo foo; }nicht). Dies wird das while Schlüsselwort jedoch nicht beschatten , da Schlüsselwörter eine höhere Priorität als Funktionen haben (die einzige Möglichkeit, diese Funktion aufzurufen, ist as \while). In bashund zshhaben Aliase eine höhere Priorität als Schlüsselwörter, daher werden sie durch Alias- Neudefinitionen von Schlüsselwörtern beschattet ( bashstandardmäßig jedoch nur in interaktiven Shells, sofern dies nicht shopt -s expand_aliasesausdrücklich aufgerufen wird).
mklement0
1

Gängige Shell-Skripte müssen häufig ihr "Home" -Verzeichnis finden, auch wenn sie als Symlink aufgerufen werden. Das Skript muss also seine "echte" Position ab nur $ 0 finden.

cat `mvn`

Auf meinem System wird ein Skript gedruckt, das Folgendes enthält. Dies sollte ein guter Hinweis darauf sein, was Sie benötigen.

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`
Hugo
quelle
1

Versuche dies:

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

Da ich im Laufe der Jahre oft darauf gestoßen bin und dieses Mal eine reine Bash-Portable-Version brauchte, die ich unter OSX und Linux verwenden konnte, schrieb ich eine:

Die lebende Version lebt hier:

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

Aber um SO willen, hier ist die aktuelle Version (ich denke, sie ist gut getestet. Aber ich bin offen für Feedback!)

Es mag nicht schwierig sein, es für eine einfache Bourne-Shell (sh) zum Laufen zu bringen, aber ich habe es nicht versucht ... Ich mag $ FUNCNAME zu sehr. :) :)

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

Hier ist ein klassisches Beispiel, dank 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

Verwenden Sie diese Funktion und es wird der -real- Pfad zurückgegeben:

%% 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 
daran interessiert
quelle
Meine frühere Antwort macht genau das Gleiche in ungefähr 1/4 des Raums :) Es ist ein ziemlich einfaches Shell-Scripting und verdient kein Git-Repo.
Dave
1

Um die Mac-Inkompatibilität zu umgehen, habe ich mir etwas ausgedacht

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

Nicht großartig, aber OS-übergreifend

Clemens Tolboom
quelle
3
Python 2.6+ ist auf mehr Endbenutzersystemen als PHP verfügbar und python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"wäre daher wahrscheinlich sinnvoller. Wenn Sie Python aus einem Shell-Skript verwenden müssen, sollten Sie wahrscheinlich nur Python verwenden und sich die Kopfschmerzen des plattformübergreifenden Shell-Skripts ersparen.
Jonathan Baldwin
Sie benötigen nicht mehr als den integrierten sh-Readlink, den Verzeichnisnamen und den Basisnamen.
Dave
@ Dave: dirname, basenameund readlinksind externe Dienstprogramme , nicht Einbauten Shell; dirnameund basenamesind Teil von POSIX, readlinkist nicht.
mklement0
@ mklement0 - Du hast ganz recht. Sie werden von CoreUtils oder einem gleichwertigen Unternehmen bereitgestellt. Ich sollte SO nicht nach 1 Uhr morgens besuchen. Das Wesentliche meines Kommentars ist, dass weder PHP noch ein anderer Sprachinterpreter als der in einem Basissystem installierte erforderlich sind. Ich habe das Skript, das ich auf dieser Seite bereitgestellt habe, seit 1997 für alle Linux-Varianten und seit 2006 für MacOS X ohne Fehler verwendet. Das OP hat nicht nach einer POSIX-Lösung gefragt. Ihre spezifische Umgebung war Mac OS X.
Dave
@ Dave: Ja, es ist möglich, dies mit Standarddienstprogrammen zu tun, aber es ist auch schwierig , dies zu tun (wie aus den Mängeln Ihres Skripts hervorgeht). Wenn OS X wirklich der Fokus waren, dann ist diese Antwort ist völlig in Ordnung - und viel einfacher - gegeben , die phpjedoch mit OS X kommt, auch wenn der Körper der Frage Nennungen OS X, ist es nicht als solche markiert, und es ist deutlich geworden , Da Menschen auf verschiedenen Plattformen hierher kommen, um Antworten zu erhalten, sollten Sie darauf hinweisen, was plattformspezifisch / nicht POSIX ist.
mklement0
1

Dies ist ein Symlink-Resolver in Bash, der unabhängig davon funktioniert, ob es sich bei dem Link um ein Verzeichnis oder um ein Nicht-Verzeichnis handelt:

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

Beachten Sie, dass alle cdund setSachen in einer Subshell stattfinden.

Solidsnack
quelle
Tatsächlich sind die {} um () nicht erforderlich, da () genau wie {} als "zusammengesetzter Befehl" gilt. Sie benötigen jedoch noch () nach dem Funktionsnamen.
Chris Cogdon
@ChrisCogdon Die {}um das ()sind nicht unnötig, wenn ()hinter dem Funktionsnamen keine stehen . Bash akzeptiert Funktionsdeklarationen ohne, ()da die Shell keine Parameterlisten in Deklarationen enthält und keine Aufrufe mit macht, ()sodass Funktionsdeklarationen mit ()nicht viel Sinn machen.
Solidsnack
0

Hier präsentiere ich eine meiner Meinung nach plattformübergreifende Lösung (zumindest Linux und MacOS) für die Antwort, die derzeit für mich gut funktioniert.

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

Hier ist eine macOS (nur?) Lösung. Möglicherweise besser für die ursprüngliche Frage geeignet.

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
}
RuneImp
quelle
0

Meine Antwort hier Bash: Wie bekomme ich einen echten Pfad für einen Symlink?

aber kurz gesagt sehr praktisch in Skripten:

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

Diese sind Teil von GNU-Coreutils, die für die Verwendung in Linux-Systemen geeignet sind.

Um alles zu testen, setzen wir symlink in / home / test2 /, ändern einige zusätzliche Dinge und führen / rufen es aus dem Stammverzeichnis auf:

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

Wo

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
Arunas Bartisius
quelle
0

Wenn pwd nicht verwendet werden kann (z. B. das Aufrufen eines Skripts von einem anderen Speicherort aus), verwenden Sie realpath (mit oder ohne Verzeichnisname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Funktioniert sowohl beim Aufrufen über (mehrere) Symlinks als auch beim direkten Aufrufen des Skripts - von jedem Ort aus.

Nakwa
quelle