Frage

Angesichts einer absoluten oder relativen Pfad (in einem Unix-ähnlichen system), ich würde wie, um zu bestimmen, den vollständigen Pfad der Ziel-nach dem auflösen einer zwischengeschalteten symlinks.Bonus-Punkte für die Lösung ~username notation zur gleichen Zeit.

Wenn das Ziel ein Verzeichnis ist, wird es möglich sein könnte, chdir() in das Verzeichnis und rufen Sie dann getcwd(), aber ich möchte wirklich, dies zu tun von einem shell-Skript eher als das schreiben einer C-Hilfsprogramm.Leider Schalen haben eine Tendenz, zu versuchen zu verbergen die Existenz Verknüpfungen des Benutzers (dies ist die 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 resolve (), so dass, wenn ausgeführt, aus dem tmp-Verzeichnis, in dem oben genannten Beispiel, Entschlossenheit("foo") == "/Users/greg/tmp/bar".

War es hilfreich?

Lösung

Gemäß den standards pwd -P sollte Rückkehr der Pfad mit symlinks behoben.

C-Funktion char *getcwd(char *buf, size_t size) von unistd.h sollte das gleiche Verhalten.

getcwd pwd

Andere Tipps

readlink -f "$path"

Editor ' s note:Die oben genannten arbeiten mit GNU readlink und FreeBSD/PC-BSD/OpenBSD readlink, aber nicht auf OS X ab 10.11.
GNU readlink bietet zusätzliche, relevante Optionen, wie -m für die Lösung eines symlink, ob oder nicht das ultimative Ziel vorhanden ist.

Hinweis: da die GNU-coreutils 8.15 (2012-01-06), es ist ein realpath Programm zur Verfügung, das ist weniger stumpf und flexibler als die oben genannten.Es ist auch kompatibel mit dem FreeBSD-util mit dem gleichen Namen.Es enthält auch Funktionen zum generieren einer relativen Pfad zwischen zwei Dateien.

realpath $path

[Admin neben unten aus Kommentar von halloleodanorton]

Für Mac OS X (mindestens durch 10.11.x) verwenden readlink ohne -f option:

readlink $path

Editor ' s note:Diese wird nicht lösen symlinks rekursiv und damit nicht Bericht die ultimate Ziel;z.B. angesichts symlink a , die Punkte zu b, die wiederum auf c, nur Bericht b (und nicht gewährleisten, dass es ist Ausgang als absoluter Pfad).
Verwenden Sie die folgenden perl Befehl in OS X, um die Lücke der fehlenden readlink -f Funktionalität:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

"pwd -P" zu funktionieren scheint, wenn Sie wollen einfach nur das Verzeichnis, aber wenn für einige Grund, Sie wollen die Namen der tatsächlichen ausführbaren ich glaube 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

Einer meiner Favoriten ist 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]

zu sein scheint, genau das, was Sie für Fragen - es nimmt eine arbirary Weg, löst alle symlinks, und gibt den "echten" Pfad - und es ist "standard *nix", dass wahrscheinlich alle Systeme, die bereits haben

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

Putting einige der angegebenen Lösungen zusammen, wohl wissend, dass readlink ist auf den meisten Systemen verfügbar, aber braucht verschiedene Argumente, das funktioniert gut für mich unter OS x und Debian.Ich bin mir nicht sicher, etwa BSD-Systeme.Vielleicht ist die Bedingung muss sein [[ $OSTYPE != darwin* ]] ausschließen -f von OSX nur.

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

Hinweis:Ich glaube, dass dies eine solide, portable, ready-made-Lösung, die von immer langen aus diesem Grunde.

Unten ist ein vollständig POSIX-konform script / Funktion das ist deshalb cross-Plattform (funktioniert auf macOS zu, deren readlink noch nicht unterstützt -f ab 10.12 (Sierra)) - es wird verwendet, nur POSIX-shell-Funktionen der Sprache und nur POSIX-kompatibles Dienstprogramm fordert.

Es ist ein portable Implementierung des GNU readlink -e (die strengere version readlink -f).

Sie können führen Sie die Skript mit sh oder Quelle Funktion in bash, ksh, und zsh:

Zum Beispiel, innerhalb eines Skripts Sie können es verwenden, wie folgt vor, um die Ausführung Schrift wahr-Verzeichnis der Ursprung, die mit symlinks behoben:

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

rreadlink script / function definition:

Der code wurde so angepasst, mit Dankbarkeit aus diese Antwort.
Ich habe auch ein bash-basierte stand-alone-Dienstprogramm-version hier, die Sie installieren können, mit
npm install rreadlink -g, wenn Sie haben 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 Sicherheit:

jarno, in Bezug auf die Funktion, sicherzustellen, dass builtin command ist nicht beschattet durch ein alias oder ein shell-Funktion mit dem gleichen Namen, fragt in einem Kommentar:

Was ist, wenn unalias oder unset und [ als Aliase oder shell-Funktionen?

Die motivation hinter rreadlink sicherstellen, dass command hat seine ursprüngliche Bedeutung ist zu verwenden Sie es auf bypass (gutartigen) Komfort Aliase und Funktionen Häufig verwendet, um Schatten, standard-Befehle im interaktiven shells, wie neu definieren ls enthalten bevorzugten Optionen.

Ich denke, es ist sicher zu sagen, dass, wenn Sie ' re Umgang mit einem nicht vertrauenswürdigen, schädliche Umwelt, sich Gedanken über unalias oder unset - oder, für diese Angelegenheit, while, do, ...- neu definiert, ist nicht von Belang.

Es ist etwas die Funktion muß sich auf seine ursprüngliche Bedeutung und Verhalten - es gibt keinen Weg darum herum.
Das POSIX-shells erlauben Neudefinition der gelieferten und auch die Sprache keywords inhärent ein Sicherheits-Risiko (und schreiben paranoid-code ist schwer im Allgemeinen).

Zu Adresse Ihre Bedenken insbesondere:

Die Funktion beruht auf unalias und unset mit Ihrer ursprünglichen Bedeutung.Mit Ihnen neu definiert als shell-Funktionen in einer Art und Weise verändert, Ihr Verhalten wäre ein problem;Neudefinition als eine alias ist nicht unbedingt ein Anliegen, weil zitieren (Teil -) Namen des Befehls (z.B., \unalias) umgeht die Aliasnamen.

Jedoch, zu zitieren ist nicht eine option für die shell keywords (while, for, if, do, ...) und während die shell-keywords zu tun haben Vorrang vor shell Funktionen, in bash und zsh Aliase haben die höchste Priorität, also zum Schutz gegen shell-Schlüsselwort neudefinitionen, die Sie ausführen müssen unalias mit Ihrem Namen (obwohl in non-interactive bash Muscheln (wie z.B. Skripte) Aliase werden nicht standardmäßig erweitert - nur, wenn shopt -s expand_aliases explizit aufgerufen wird zuerst).

Um sicherzustellen, dass unalias - als builtin - hat seine ursprüngliche Bedeutung, die Sie verwenden müssen \unset auf ihn zuerst, die erfordert, dass unset seine ursprüngliche Bedeutung:

unset ist ein shell - builtin, also, um sicherzustellen, dass es aufgerufen wird, als solche, Sie würde haben zu machen sicher, dass es sich nicht neu definiert als Funktion.Während Sie umgehen kann eine alias-form mit zitieren, die Sie nicht umgehen können, eine shell-Funktion - catch-22.

So, es sei denn, Sie können sich darauf verlassen, unset seine ursprüngliche Bedeutung, von dem, was ich sagen kann, es gibt keine garantierte Möglichkeit, zu verteidigen gegen alle bösartigen neudefinitionen.

Common shell-Skripts, oft zu finden, dass Ihre "home" - Verzeichnis, auch wenn Sie angerufen werden, als einen symlink.Das script so zu finden, Ihr "real" - position von nur $0.

cat `mvn`

auf meinem system druckt ein Skript mit den folgenden, was einen guten Hinweis auf das, was Sie brauchen.

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"))/..

Versuchen Sie dies:

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

Da habe ich dieses viele Male im Laufe der Jahre, und dieses mal brauchte ich ein reines bash-portable-version, die ich verwenden könnte, für OSX und linux, ich ging voran und schrieb ein:

Die Wohn-version lebt hier:

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

aber für den Willen ALSO, hier ist die aktuelle version (ich glaube, es ist gut getestet..aber ich bin offen für feedback!)

Vielleicht nicht schwer zu machen es Arbeit für plain bourne-shell (sh), aber ich habe nicht versucht...ich mag $FUNCNAME zu viel.:)

#!/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 zu brauen:

%% 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 erfolgt die Rückkehr der echten Pfad:

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

Arbeiten, um die Mac-Inkompatibilität, ich kam mit

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

Nicht groß, aber cross-OS

Hier ist, wie man mit dem Pfad zu der Datei im MacOS - /Unix mithilfe von inline-Perl-Skript:

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

Ebenso, um das Verzeichnis einer symlinked Datei:

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

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

(cd "$DIR"; pwd -P)

Jedoch, wenn es vielleicht eine Datei, dann funktioniert es nicht:

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

weil der symlink könnten lösen, in einen relativen oder absoluten Pfad.

Auf scripts, die ich brauchen, um zu finden die tatsächlichen Pfad, so dass ich vielleicht Referenz-Konfiguration oder sonstigen Skripts verwenden, installiert zusammen mit ihm, ich benutze dieses:

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önnte set SOURCE zu jeder Datei-Pfad.Im Grunde, so lange, wie Sie den Pfad ein symbolischer Link, behebt es, dass der symlink.Der trick ist, in der letzten Zeile der Schleife.Wenn die gelöst symlink ist absolut, es verwenden, als SOURCE.Allerdings, wenn es relativ ist, werden voranstellen DIR für ihn, das war aufgelöst in einer realen Lage der einfache trick, den ich zuerst beschrieben.

Ich glaube, das ist der wahre und endgültige "Lösung für ein symlink" ob es ein Verzeichnis oder eine nicht-Verzeichnis, mithilfe der Bash-Shell:

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

Beachten Sie, dass alle cd und set Sachen erfolgt in einer subshell.

Hier präsentiere ich, was ich glaube zu sein, ein cross-Plattform (Linux und macOS mindestens) eine Lösung auf die Antwort, die gut funktioniert für mich derzeit.

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 ein macOS (nur?) Lösung.Möglicherweise besser geeignet, um die ursprüngliche Frage.

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
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top