web-dev-qa-db-de.com

Wie normalisieren Sie einen Dateipfad in Bash?

Ich möchte /foo/bar/.. in /foo umwandeln

Gibt es einen bash-Befehl, der dies tut?


Edit: In meinem praktischen Fall existiert das Verzeichnis.

164
Fabien

wenn Sie einen Teil eines Dateinamens aus dem Pfad abändern möchten, sind "dirname" und "basename" Ihre Freunde, und "realpath" ist auch 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 realpath von Ihrer Shell nicht unterstützt wird, können Sie es versuchen 

readlink -f /path/here/.. 

Ebenfalls

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

Funktioniert wie 

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

der Pfad muss nicht vorhanden sein, um normalisiert zu werden. 

161
Kent Fredric

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

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

und es funktioniert gut.

86
Tim Whitcomb

Versuchen Sie realpath. Unten ist die Quelle in ihrer Gesamtheit, hiermit an die Public Domain gespendet.

// 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);
}
53
Adam Liss

Eine tragbare und zuverlässige Lösung ist die Verwendung von Python, das praktisch ü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, wobei ein kanonischer Pfad generiert wird:

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

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

35
loevborg

Verwenden Sie das Dienstprogramm readlink aus dem coreutils-Paket.

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

readlink ist der Bash-Standard zum Erhalten des absoluten Pfads. Es hat auch den Vorteil, leere Zeichenfolgen zurückzugeben, wenn Pfade oder ein Pfad nicht vorhanden sind (wenn die Flags dies tun).

Um den absoluten Pfad zu einem Verzeichnis zu erhalten, das möglicherweise existiert oder nicht, aber dessen Eltern vorhanden sind, verwenden Sie:

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 sie vorhanden sind, ignorieren Sie ansonsten fehlende Verzeichnisse und geben Sie den Pfad trotzdem zurück.

abspath=$(readlink -m $path)

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

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

Das wird in den Verzeichnis-Teil von $ path geleitet und das aktuelle Verzeichnis wird zusammen mit dem Dateiteil von $ path gedruckt. Wenn chdir fehlschlägt, erhalten Sie eine leere Zeichenfolge und einen Fehler in stderr.

13
Craig

Alte Frage, aber es geht viel einfacher wenn Sie mit vollständigen Pfadnamen zu tun haben auf der Shell-Ebene:

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

Da die CD in einer Subshell abläuft, hat dies keinen Einfluss auf das Hauptskript.

Zwei Varianten, vorausgesetzt, Ihre integrierten Shell-Befehle akzeptieren -L und -P:

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

Ich persönlich brauche diesen späteren Ansatz selten, es sei denn, ich bin aus irgendeinem Grund fasziniert von symbolischen Links.

FYI: Variation beim Abrufen des Startverzeichnisses eines Skripts, das 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 über das absolute Verzeichnis verfügen, auch wenn das Skript von Befehlen wie ./script.sh ausgeführt wird, die ohne cd/pwd häufig nur Folgendes ergibt. 

7
Gilbert

Wie Adam Liss feststellte, ist realpath nicht in jeder Distribution enthalten. Was schade ist, denn es ist die beste Lösung. Der bereitgestellte Quellcode ist großartig und ich werde ihn wahrscheinlich jetzt verwenden. Das, was ich bis jetzt verwendet habe, das ich hier nur 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"
} 

Soll Symlinks aufgelöst werden, ersetzen Sie einfach pwd durch pwd -P.

7
Jeet

Meine letzte Lösung war:

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

Basierend auf der Antwort von Tim Whitcomb.

7
schmunk

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

readlink ist in Ordnung, wenn Sie tatsächlich Symlinks folgen möchten. Es gibt aber auch einen Anwendungsfall für die bloße Normalisierung von ./- und ../- und //-Sequenzen, die rein syntaktisch ausgeführt werden können, ohne - kanonische Symbolverknüpfungen. readlink ist dafür nicht geeignet, und realpath ist auch nicht geeignet.

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

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

Ein sed-Skript 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 die Ausgabe von sed an ihre 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
5
Jesse Glick

Gesprächig und eine späte Antwort. Ich muss eins schreiben, da ich auf älterem RHEL4/5 stecken bleibe . Ich kümmere mich um absolute und relative Links und vereinfacht //, /./ und somedir /../.

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`
4
alhernau

Ich bin spät dran, aber dies ist die Lösung, die ich gemacht habe, nachdem ich ein paar Threads wie diese gelesen hatte:

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

Dies löst den absoluten Pfad von $ 1 auf, spielt Nice mit ~, hält Symlinks in dem Pfad, in dem sie sich befinden, und stört den Verzeichnisstapel nicht. Es gibt den vollständigen Pfad oder nichts zurück, wenn es nicht existiert. Es erwartet, dass $ 1 ein Verzeichnis ist, und wird wahrscheinlich fehlschlagen, wenn dies nicht der Fall ist. Dies ist jedoch eine einfache Überprüfung.

4
apottere

Testen Sie unser neues Bash-Bibliotheksprodukt realpath-lib , das wir kostenlos und unbeschränkt auf GitHub gestellt haben. Es ist ausführlich dokumentiert und ist ein großartiges Lernwerkzeug. 

Es löst lokale, relative und absolute Pfade auf und hat außer Bash 4+ keine Abhängigkeiten. es sollte also ü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 für get_dirname, get_filename, get_ stammname und validate_path. Versuchen Sie es plattformübergreifend und verbessern Sie es.

3
AsymLabs

Basierend auf @ Andres Antwort könnte ich eine etwas bessere Version haben, falls jemand eine schleifenfreie, vollständig auf String-Manipulation basierende Lösung sucht. Dies ist auch für diejenigen nützlich, die keine Symlinks ableiten möchten. Dies ist der Nachteil der Verwendung von realpath oder readlink -f.

Es funktioniert mit den 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
2

Das Problem mit realpath ist, dass es nicht in BSD (oder OSX für diese Angelegenheit) verfügbar ist. Hier ist ein einfaches Rezept aus einem ziemlich alten (2009) Artikel aus dem Linux Journal , das 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, dass für diese Variante auch not der Pfad vorhanden ist.

1
André Anjos

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

  • Arbeit an einem serienmäßigen Mac. realpath und readlink -f sind Addons
  • Lösen Sie Symlinks
  • Fehlerbehandlung haben

Keine der Antworten hatte sowohl # 1 als auch # 2. Ich fügte Nummer 3 hinzu, um andere Yak-Rasuren zu retten.

#!/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 Leerstellen in den Pfaden, um das Zitieren vollständig auszuüben

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
David Blevins

Basierend auf Loveborgs hervorragendem Python-Snippet schrieb ich Folgendes:

#!/bin/sh

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

for file in "[email protected]"; 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
Edward Falk
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

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

0
user240515

Ich weiß, das ist eine alte Frage. Ich biete immer noch eine Alternative an. Kürzlich traf ich das gleiche Problem und fand keinen vorhandenen und tragbaren Befehl, um dies zu tun. Also habe ich das folgende Shell-Skript geschrieben, das eine Funktion enthält, die den Trick beherrscht.

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