Wie normalisiert man einen Dateipfad in Bash?


203

Ich möchte zu transformieren /foo/bar/..zu/foo

Gibt es einen Bash-Befehl, der dies tut?


Bearbeiten: In meinem praktischen Fall existiert das Verzeichnis.


4
Ist es wichtig, ob /foo/baroder sogar /footatsächlich vorhanden, oder interessieren Sie sich nur für den Aspekt der Zeichenfolgenmanipulation gemäß den Pfadnamenregeln?
Chen Levy

5
@ Walwal ... das ist irgendwie erfunden ...
Camilo Martin

2
@CamiloMartin Überhaupt nicht erfunden - es macht genau das, was die Frage stellt - transformiert sich /foo/bar/..in /fooeinen bashBefehl und verwendet ihn . Wenn es andere Anforderungen gibt, die nicht angegeben sind, dann sollten sie vielleicht ...
Twalberg

14
@twalberg Sie haben zu viel TDD -_- '
Camilo Martin

Antworten:


192

Wenn Sie einen Teil eines Dateinamens aus dem Pfad entfernen möchten, sind "dirname" und "basename" Ihre Freunde, und "realpath" ist ebenfalls praktisch.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath Alternativen

Wenn realpathIhre Shell dies nicht unterstützt, können Sie es versuchen

readlink -f /path/here/.. 

Ebenfalls

readlink -m /path/there/../../ 

Funktioniert genauso wie

realpath -s /path/here/../../

, dass der Pfad nicht existieren muss, um normalisiert zu werden.


5
Für diejenigen unter Ihnen, die eine OS X-Lösung benötigen, lesen Sie die Antwort von Adam Liss unten.
Trenton

stackoverflow.com/a/17744637/999943 Dies ist eine stark verwandte Antwort! Ich bin an einem Tag auf diese beiden QS-Beiträge gestoßen und wollte sie miteinander verknüpfen.
Phyatt

realpath scheint 2012 zu coreutils hinzugefügt worden zu sein. Siehe den Dateiversionsverlauf unter github.com/coreutils/coreutils/commits/master/src/realpath.c .
Noah Lavine

1
Beide realpathund readlinkstammen von den GNU-Kerndienstprogrammen, sodass Sie höchstwahrscheinlich entweder beide oder keine erhalten. Wenn ich mich richtig erinnere, unterscheidet sich die Readlink-Version auf dem Mac geringfügig von der GNU-Version: - \
Antoine 'hashar' Musso

100

Ich weiß nicht, ob es einen direkten Bash-Befehl gibt, aber ich tue es normalerweise

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

und es funktioniert gut.


9
Dadurch werden Softlinks normalisiert, aber nicht aufgelöst. Dies kann entweder ein Fehler oder eine Funktion sein. :-)
Adam Liss

4
Es gibt auch ein Problem, wenn $ CDPATH definiert ist. weil die "cd foo" in jedes "foo" -Verzeichnis wechselt, das ein Unterverzeichnis von $ CDPATH ist, nicht nur in ein "foo", das sich im aktuellen Verzeichnis befindet. Ich denke, Sie müssen etwas tun wie: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P.
mjs

7
Tims Antwort ist definitiv die einfachste und tragbarste. CDPATH ist einfach zu handhaben: dir = "$ (nicht gesetztes CDPATH && cd" $ dir "&& pwd)"
David Blevins

2
Dies kann sehr gefährlich sein ( rm -rf $normalDir), wenn dirToNormalize nicht existiert!
Frank Meulenaar

4
Ja, es ist wahrscheinlich am besten, einen &&Kommentar gemäß @ DavidBlevins zu verwenden.
Elias Dorneles

54

Versuchen Sie es realpath. Nachfolgend finden Sie die Quelle in ihrer Gesamtheit, die hiermit der Öffentlichkeit zugänglich gemacht wurde.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}

1
Ist dies Teil der Standard-Bash-Installation? Ich bekomme "Befehl nicht gefunden" auf unserem System (Bash 3.00.15 (1) auf RHEL)
Tim Whitcomb

4
gnu.org/software/coreutils für readlink, aber realpath stammt aus diesem Paket: packages.debian.org/unstable/utils/realpath
Kent Fredric

8
Es ist Standard auf Ubuntu / BSD, nicht Centos / OSX
Erik Aronesty

4
Sie sollten den Teil wirklich mit "Bonus: Es ist ein Bash-Befehl und überall verfügbar!" Durchstreichen. Teil über realpath. Es ist auch mein Favorit, aber anstatt Ihr Tool von der Quelle zu ergänzen. Es ist alles zu schwer, und die meiste Zeit willst du nur readlink -f... Übrigens, es readlinkist auch kein eingebauter Bash, sondern Teil von coreutilsUbuntu.
Tomasz Gandor

4
Sie haben gesagt, Sie spenden dies an die Public Domain, aber in der Kopfzeile steht auch "für jede gemeinnützige Verwendung" - wollen Sie es wirklich an die Public Domain spenden? Das würde bedeuten, dass es sowohl für kommerzielle Zwecke als auch für gemeinnützige Zwecke verwendet werden kann…
me_and

36

Eine tragbare und zuverlässige Lösung ist die Verwendung von Python, das fast überall vorinstalliert ist (einschließlich Darwin). Sie haben zwei Möglichkeiten:

  1. abspath Gibt einen absoluten Pfad zurück, löst jedoch keine Symlinks auf:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath Gibt einen absoluten Pfad zurück und löst dabei Symlinks auf, wodurch ein kanonischer Pfad generiert wird:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

In jedem Fall path/to/filekann entweder ein relativer oder ein absoluter Pfad sein.


7
Danke, das war der einzige, der funktioniert hat. Readlink oder Realpath sind unter OS X nicht verfügbar. Python sollte auf den meisten Plattformen verfügbar sein.
Sorin

1
Nur zur Verdeutlichung, readlinkist unter OS X verfügbar, nur nicht mit der -fOption. Tragbare Abhilfen diskutiert hier .
StvnW

3
Es verwirrt mich buchstäblich, dass dies die einzig vernünftige Lösung ist, wenn Sie keinen Links folgen möchten. Der Unix-Weg mein FUSS.
DannoHung

34

Verwenden Sie das Dienstprogramm readlink aus dem Paket coreutils.

MY_PATH=$(readlink -f "$0")

1
BSD hat nicht das Flag -f, was bedeutet, dass dies selbst auf dem neuesten MacOS Mojave und vielen anderen Systemen fehlschlägt. Vergessen Sie die Verwendung von -f, wenn Sie Portabilität wünschen, da viele Betriebssysteme betroffen sind.
Sorin

1
@ Sorin. Die Frage betrifft nicht den Mac, sondern Linux.
Mattalxndr

14

readlinkist der Bash-Standard zum Erhalten des absoluten Pfades. Es hat auch den Vorteil, leere Zeichenfolgen zurückzugeben, wenn Pfade oder ein Pfad nicht vorhanden sind (vorausgesetzt, die Flags dazu).

Verwenden Sie Folgendes, um den absoluten Pfad zu einem Verzeichnis zu erhalten, das möglicherweise vorhanden ist oder nicht, dessen Eltern jedoch vorhanden sind:

abspath=$(readlink -f $path)

So erhalten Sie den absoluten Pfad zu einem Verzeichnis, das zusammen mit allen Eltern vorhanden sein muss:

abspath=$(readlink -e $path)

Um den angegebenen Pfad zu kanonisieren und Symlinks zu folgen, falls vorhanden, aber ansonsten fehlende Verzeichnisse zu ignorieren und den Pfad trotzdem zurückzugeben, ist es:

abspath=$(readlink -m $path)

Der einzige Nachteil ist, dass Readlink Links folgt. Wenn Sie Links nicht folgen möchten, können Sie diese alternative Konvention verwenden:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

Dadurch wird der Verzeichnisteil von $ path geändert und das aktuelle Verzeichnis zusammen mit dem Dateiteil von $ path gedruckt. Wenn chdir nicht funktioniert, erhalten Sie eine leere Zeichenfolge und einen Fehler auf stderr.


7
readlinkist eine gute Option, wenn es verfügbar ist. Die OS X-Version unterstützt die Optionen -eoder nicht -f. In den ersten drei Beispielen sollten doppelte Anführungszeichen vorhanden sein $path, um Leerzeichen oder Platzhalter im Dateinamen zu verarbeiten. +1 für die Parametererweiterung, dies hat jedoch eine Sicherheitslücke. Wenn pathes leer ist, wird dies cdin Ihr Home-Verzeichnis verschoben. Sie benötigen doppelte Anführungszeichen. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
Toxalot

Dies war nur ein Beispiel. Wenn Sie auf Sicherheit aus sind, sollten Sie Bash oder eine andere Shell-Variante überhaupt nicht verwenden. Außerdem hat bash seine eigenen Probleme, wenn es um plattformübergreifende Kompatibilität geht, sowie Probleme mit Funktionsänderungen zwischen Hauptversionen. OSX ist nur eine von vielen Plattformen mit Problemen im Zusammenhang mit Shell-Skripten, ganz zu schweigen davon, dass es auf BSD basiert. Wenn Sie wirklich plattformübergreifend sein müssen, müssen Sie POSIX-kompatibel sein, damit die Parametererweiterung wirklich aus dem Fenster geht. Schauen Sie sich einige Zeit Solaris oder HP-UX an.
Craig

6
Es ist wichtig, hier keine Beleidigung zu bedeuten, aber auf obskure Themen wie diese hinzuweisen. Ich möchte nur eine schnelle Antwort auf dieses triviale Problem und hätte diesem Code mit allen Eingaben vertraut, wenn der obige Kommentar nicht gewesen wäre. Es ist auch wichtig, OS-X in diesen Bash-Diskussionen zu unterstützen. Es gibt viele Befehle, die unter OS-X leider nicht unterstützt werden, und viele Foren halten dies für selbstverständlich, wenn es um Bash geht. Dies bedeutet, dass wir weiterhin viele plattformübergreifende Probleme haben werden, es sei denn, sie werden eher früher als später behandelt.
Rebs

13

Alte Frage, aber es gibt einen viel einfacheren Weg, wenn Sie mit vollständigen Pfadnamen auf Shell-Ebene arbeiten:

   abspath = "$ (cd" $ path "&& pwd)"

Da die CD in einer Subshell ausgeführt wird, hat dies keine Auswirkungen auf das Hauptskript.

Angenommen, Ihre in die Shell integrierten Befehle akzeptieren -L und -P, sind zwei Varianten:

   abspath = "$ (cd -P" $ path "&& pwd -P)" #physischer Pfad mit aufgelösten Symlinks
   abspath = "$ (cd -L" $ path "&& pwd -L)" #logischer Pfad, der Symlinks beibehält

Persönlich brauche ich diesen späteren Ansatz nur selten, wenn ich aus irgendeinem Grund von symbolischen Links fasziniert bin.

Zu Ihrer Information: Variation beim Abrufen des Startverzeichnisses eines Skripts, die auch dann funktioniert, wenn das Skript später das aktuelle Verzeichnis ändert.

name0 = "$ (Basisname" $ ​​0 ")"; #Basisname des Skripts
dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute Startverzeichnis

Die Verwendung von CD stellt sicher, dass Sie immer das absolute Verzeichnis haben, auch wenn das Skript von Befehlen wie ./script.sh ausgeführt wird, die ohne cd / pwd oft nur ... Unnütz sind, wenn das Skript später eine CD erstellt.


8

Wie Adam Liss feststellte, realpathwird nicht mit jeder Distribution gebündelt. Was schade ist, denn es ist die beste Lösung. Der bereitgestellte Quellcode ist großartig und ich werde ihn wahrscheinlich jetzt verwenden. Folgendes habe ich bisher verwendet, das ich hier der Vollständigkeit halber teile:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

Wenn Sie es zu lösen Symlinks möchten, ersetzen Sie einfach pwdmit pwd -P.


Ein Gotcha mit der pwd -POption für diesen Fall ... Überlegen Sie, was passieren würde, wenn $(basename "$1")ein Symlink zu einer Datei in einem anderen Verzeichnis wäre. Der pwd -Peinzige löst Symlinks im Verzeichnisbereich des Pfads auf, nicht jedoch im Basisnamenbereich.
Toxalot

7

Meine jüngste Lösung war:

pushd foo/bar/..
dir=`pwd`
popd

Basierend auf der Antwort von Tim Whitcomb.


Ich vermute, dass dies fehlschlägt, wenn das Argument kein Verzeichnis ist. Angenommen, ich möchte wissen, wohin / usr / bin / java führt?
Edward Falk

1
Wenn Sie wissen, dass es sich um eine Datei handelt, können Sie es pushd $(dirname /usr/bin/java)versuchen.
schmunk

5

Nicht gerade eine Antwort, aber vielleicht eine Folgefrage (die ursprüngliche Frage war nicht explizit):

readlinkist in Ordnung, wenn Sie tatsächlich Symlinks folgen möchten. Es gibt aber auch einen Anwendungsfall für das bloße Normalisieren von ./und ../und //Sequenzen, der rein syntaktisch durchgeführt werden kann, ohne Symlinks zu kanonisieren. readlinkist nicht gut dafür und auch nicht realpath.

for f in $paths; do (cd $f; pwd); done

funktioniert für vorhandene Pfade, bricht aber für andere.

Ein sedSkript scheint eine gute Wahl zu sein, außer dass Sie Sequenzen ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) nicht iterativ ersetzen können, ohne etwas wie Perl zu verwenden, das nicht auf allen Systemen sicher angenommen werden kann, oder eine hässliche Schleife zu verwenden, um die Ausgabe von sedto zu vergleichen seine Eingabe.

FWIW, ein Einzeiler mit Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths

realpatheine hat -sOption, keine symbolischen Links zu lösen und nur Entschlossenheit Verweise auf /./, /../und zusätzliche entfernen /Zeichen. In Kombination mit der -mOption wird realpathnur der Dateiname verarbeitet und keine tatsächliche Datei berührt. Es klingt nach der perfekten Lösung. Aber leider realpathfehlt immer noch auf vielen Systemen.
Toxalot

Das Entfernen von ..Komponenten kann nicht syntaktisch erfolgen, wenn Symlinks beteiligt sind. /one/two/../threewie ist nicht das gleiche , /one/threewenn twoein Symlink ist /foo/bar.
jrw32982 unterstützt Monica

@ jrw32982 Ja, wie ich in meiner Antwort sagte, ist dies für einen Anwendungsfall, in dem eine Symlink-Kanonisierung nicht gewünscht oder benötigt wird.
Jesse Glick

@JesseGlick Es geht nicht nur darum, ob Sie Symlinks kanonisieren möchten oder nicht. Ihr Algorithmus liefert tatsächlich die falsche Antwort. Damit Ihre Antwort richtig ist, müssen Sie a priori wissen, dass keine Symlinks vorhanden sind (oder dass sie nur eine bestimmte Form haben). Ihre Antwort besagt, dass Sie sie nicht kanonisieren möchten , nicht, dass der Pfad keine Symlinks enthält.
jrw32982 unterstützt Monica

Es gibt Anwendungsfälle, in denen die Normalisierung durchgeführt werden muss, ohne eine feste, vorhandene Verzeichnisstruktur anzunehmen. Die URI-Normalisierung ist ähnlich. In diesen Fällen ist es eine inhärente Einschränkung, dass das Ergebnis im Allgemeinen nicht korrekt ist, wenn sich in der Nähe eines Verzeichnisses, in dem das Ergebnis später angewendet wird, Symlinks befinden.
Jesse Glick

5

Ich bin zu spät zur Party, aber dies ist die Lösung, die ich entwickelt habe, nachdem ich eine Reihe von Themen wie diese gelesen habe:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

Dies wird den absoluten Pfad von $ 1 auflösen, gut mit ~ spielen, Symlinks in dem Pfad behalten, in dem sie sich befinden, und es wird nicht mit Ihrem Verzeichnisstapel in Konflikt geraten. Es gibt den vollständigen Pfad oder nichts zurück, wenn es nicht existiert. Es wird erwartet, dass 1 US-Dollar ein Verzeichnis ist, und es wird wahrscheinlich fehlschlagen, wenn dies nicht der Fall ist. Dies ist jedoch eine einfache Überprüfung, die Sie selbst durchführen können.


4

Gesprächige und etwas späte Antwort. Ich muss einen schreiben, da ich auf älterem RHEL4 / 5 stecke. Ich behandle absolute und relative Links und vereinfache //, /./ und somedir /../ Einträge.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`

3

Testen Sie unser neues Bash-Bibliotheksprodukt realpath-lib , das wir kostenlos und unbeschwert auf GitHub eingestellt haben. Es ist gründlich dokumentiert und ein großartiges Lernwerkzeug.

Es löst lokale, relative und absolute Pfade auf und hat keine Abhängigkeiten außer Bash 4+. es sollte also fast überall funktionieren. Es ist kostenlos, sauber, einfach und lehrreich.

Du kannst tun:

get_realpath <absolute|relative|symlink|local file path>

Diese Funktion ist der Kern der Bibliothek:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

Es enthält auch Funktionen zu get_dirname, get_filename, get_ stemname und validate_path. Probieren Sie es plattformübergreifend aus und helfen Sie, es zu verbessern.


2

Das Problem dabei realpathist, dass es unter BSD (oder OSX) nicht verfügbar ist. Hier ist ein einfaches Rezept, das aus einem ziemlich alten Artikel (2009) aus dem Linux Journal extrahiert wurde und ziemlich portabel ist:

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Beachten Sie auch diese Variante nicht nicht den Pfad erfordern zu existieren.


Dies löst jedoch keine Symlinks. Realpath verarbeitet Pfade, die an der Wurzel beginnen, und folgt im Verlauf Symlinks. Dies führt lediglich dazu, dass übergeordnete Referenzen reduziert werden.
Martijn Pieters

2

Basierend auf der Antwort von @ Andre könnte ich eine etwas bessere Version haben, falls jemand nach einer schleifenfreien, vollständig auf String-Manipulation basierenden Lösung sucht. Es ist auch nützlich für diejenigen, die keine Symlinks dereferenzieren möchten, was der Nachteil der Verwendung von realpathoder ist readlink -f.

Es funktioniert mit Bash-Versionen 3.2.25 und höher.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config

Dies ist eine nette Idee, aber ich habe 20 Minuten damit verbracht, dies für verschiedene Versionen von Bash zum Laufen zu bringen. Es stellt sich heraus, dass die Extglob-Shell-Option aktiviert sein muss, damit dies funktioniert, und dies ist nicht standardmäßig der Fall. Wenn es um Bash-Funktionen geht, ist es wichtig, sowohl die erforderliche Version als auch nicht standardmäßige Optionen anzugeben, da diese Details zwischen den Betriebssystemen variieren können. Beispielsweise enthält eine neuere Version von Mac OSX (Yosemite) nur eine veraltete Version von bash (3.2).
Drwatsoncode

Entschuldigung @ricovox; Ich habe diese jetzt aktualisiert. Ich bin gespannt auf die genaue Version von Bash, die Sie dort haben. Die obige Formel (aktualisiert) funktioniert unter CentOS 5.8, das mit Bash 3.2.25
ϹοδεMεδιϲ

Entschuldigung für die Verwirrung. Dieser Code funktionierte auf meiner Version von Mac OSX Bash (3.2.57), nachdem ich extglob aktiviert hatte. Mein Hinweis zu Bash-Versionen war allgemeiner (was hier eher für eine andere Antwort in Bezug auf Regex in Bash gilt).
Drwatsoncode

2
Ich freue mich jedoch über Ihre Antwort. Ich habe es als Basis für meine eigenen benutzt. Im Übrigen sind mir mehrere Fälle aufgefallen, in denen Ihre fehlgeschlagen sind: (1) Relative Pfade hello/../world(2) Punkte im Dateinamen /hello/..world(3) Punkte nach dem doppelten Schrägstrich /hello//../world(4) Punkt vor oder nach dem doppelten Schrägstrich /hello//./worldoder /hello/.//world (5) Eltern nach dem aktuellen: /hello/./../world/ (6) Eltern after Parent: /hello/../../worldusw. - Einige davon können mithilfe einer Schleife behoben werden, um Korrekturen vorzunehmen, bis sich der Pfad nicht mehr ändert. (Entfernen Sie auch dir/../, nicht /dir/..aber entfernen Sie dir/..vom Ende.)
drwatsoncode

0

Basierend auf Loveborgs exzellentem Python-Snippet habe ich Folgendes geschrieben:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done

0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

Dies funktioniert auch dann, wenn die Datei nicht vorhanden ist. Das Verzeichnis, in dem sich die Datei befindet, muss vorhanden sein.


Für GNU Realpath muss auch nicht das letzte Element im Pfad vorhanden sein, es sei denn, Sie verwendenrealpath -e
Martijn Pieters

0

Ich brauchte eine Lösung, die alle drei Aufgaben erfüllt:

  • Arbeiten Sie an einem Standard-Mac. realpathund readlink -fsind Addons
  • Symlinks auflösen
  • Fehlerbehandlung haben

Keine der Antworten hatte sowohl # 1 als auch # 2. Ich habe # 3 hinzugefügt, um anderen die weitere Yak-Rasur zu ersparen.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Hier ist ein kurzer Testfall mit einigen verdrehten Leerzeichen in den Pfaden, um das Zitat vollständig auszuführen

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "

0

Ich weiß, dass dies eine alte Frage ist. Ich biete immer noch eine Alternative an. Kürzlich bin ich auf dasselbe Problem gestoßen und habe keinen vorhandenen und portablen Befehl dafür gefunden. Also habe ich das folgende Shell-Skript geschrieben, das eine Funktion enthält, die den Trick ausführen kann.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c


0

Ich habe eine integrierte Funktion entwickelt, um dies zu handhaben, wobei der Schwerpunkt auf der höchstmöglichen Leistung liegt (zum Spaß). Symlinks werden nicht aufgelöst, daher ist es im Grunde dasselbe wie realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Hinweis: Diese Funktion verarbeitet keine Pfade, die mit beginnen //, da genau zwei doppelte Schrägstriche am Anfang eines Pfads ein implementierungsdefiniertes Verhalten sind. Es behandelt jedoch /,/// und so weiter gut.

Diese Funktion scheint alle Randfälle richtig zu behandeln, aber es gibt möglicherweise noch einige, mit denen ich mich nicht befasst habe.

Leistungshinweis: Wird beim Aufrufen mit Tausenden von Argumenten abspathetwa 10-mal langsamer ausgeführt als realpath -sm; Wenn es mit einem einzelnen Argument aufgerufen wird, abspathläuft es> 110x schneller als realpath -smauf meinem Computer, hauptsächlich weil nicht jedes Mal ein neues Programm ausgeführt werden muss.


-1

Ich habe heute festgestellt, dass Sie den statBefehl zum Auflösen von Pfaden verwenden können.

Also für ein Verzeichnis wie "~ / Documents":

Sie können dies ausführen:

stat -f %N ~/Documents

So erhalten Sie den vollständigen Pfad:

/Users/me/Documents

Für Symlinks können Sie die Option% Y-Format verwenden:

stat -f %Y example_symlink

Welches könnte ein Ergebnis zurückgeben wie:

/usr/local/sbin/example_symlink

Die Formatierungsoptionen können in anderen Versionen von * NIX unterschiedlich sein, aber diese haben unter OSX für mich funktioniert.


1
Die stat -f %N ~/DocumentsLinie ist ein roter Hering ... Ihre Schale ersetzt ~/Documentsdurch /Users/me/Documentsund statdruckt nur das Argument wörtlich.
Danwyand

-4

Eine einfache Lösung mit node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.