So rufen Sie den relativen Pfad ab, der relativ angegeben ist


159

Gibt es einen Befehl zum Abrufen des absoluten Pfads bei gegebenem relativen Pfad?

Zum Beispiel möchte ich, dass $ line den absoluten Pfad jeder Datei in dir enthält ./etc/

find ./ -type f | while read line; do
   echo $line
done


1
kein striktes Duplikat, aber ähnlich
mpapis


Eine viel bessere Lösung als jede der bisher aufgeführten ist hier, wie man einen relativen Pfad in einen absoluten Pfad in Unix konvertiert
Daniel Genin

Antworten:


66

verwenden:

find "$(pwd)"/ -type f

um alle Dateien zu bekommen oder

echo "$(pwd)/$line"

um den vollständigen Pfad anzuzeigen (wenn der relative Pfad wichtig ist)


2
Was ist, wenn ich einen relativen Pfad in der Suche angeben?
Nubme

17
Dann sehen Sie die Links im Kommentar zu Ihrer Frage. Der beste Weg ist wahrscheinlich 'readlink -f $ line'
mpapis

3
Warum $(pwd)anstelle von verwenden $PWD? (Ja, ich weiß, pwdist ein eingebautes)
Adam Katz

178

Versuchen Sie es realpath.

~ $ sudo apt-get install realpath  # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc

Verwenden Sie, um das Erweitern von Symlinks zu vermeiden realpath -s.

Die Antwort kommt von " Befehl bash / fish, um den absoluten Pfad zu einer Datei zu drucken ".


11
realpathscheint auf dem Mac nicht verfügbar zu sein (OS X 10.11 "El Capitan"). :-(
Laryx Decidua

realpathscheint auch unter CentOS 6 nicht verfügbar zu sein
user5359531

6
auf osx, brew install coreutilswird inrealpath
Kevin Chen

Auf meinem Ubuntu 18.04 realpathist schon vorhanden. Ich musste es nicht separat installieren.
Acumenus

Überraschenderweise realpathist es auf Git für Windows verfügbar (zumindest für mich).
Andrew Keeton

104

Wenn Sie das Paket coreutils installiert haben, können Sie es im Allgemeinen verwenden, readlink -f relative_file_nameum das absolute Paket abzurufen (wobei alle Symlinks aufgelöst sind).


3
Das Verhalten hierfür unterscheidet sich ein wenig von dem, was der Benutzer verlangt. Es folgt und löst auch rekursive Symlinks an einer beliebigen Stelle im Pfad auf. In einigen Fällen möchten Sie das möglicherweise nicht.
Ffledgling

1
@BradPeabody Dies funktioniert auf einem Mac, wenn Sie Coreutils von Homebrew installieren brew install coreutils. Die ausführbare Datei wird jedoch von ag vorangestellt:greadlink -f relative_file_name
Miguel Isla

2
Beachten Sie, dass die Handbuchseite von readlink (1) als ersten Satz ihrer Beschreibung Folgendes enthält: "Beachten Sie, dass realpath (1) der bevorzugte Befehl für die Kanonisierungsfunktionalität ist."
Josch

1
Sie können -e anstelle von -f verwenden, um zu überprüfen, ob die Datei / das Verzeichnis vorhanden ist oder nicht
Iceman

69
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

UPD Einige Erklärungen

  1. Dieses Skript erhält einen relativen Pfad als Argument "$1"
  2. Dann bekommen wir DIRNAME Teil dieses Weges (können Sie entweder dir oder die Datei zu diesem Skript übergeben):dirname "$1"
  3. Dann gehen wir cd "$(dirname "$1")in dieses relative Verzeichnis und erhalten den absoluten Pfad dafür, indem wir den pwdShell-Befehl ausführen
  4. Danach hängen wir den Basisnamen an den absoluten Pfad an:$(basename "$1")
  5. Als letzten Schritt haben wir echoes geschafft

8
Diese Antwort verwendet die beste Bash-Sprache
Rondo

2
readlink ist die einfache Lösung für Linux, aber diese Lösung funktioniert auch unter OSX, also +1
thetoolman

1
Dieses Skript entspricht nicht dem, was realpathoder was zu readlink -ftun ist. Beispielsweise funktioniert es nicht auf Pfaden, bei denen die letzte Komponente ein Symlink ist.
Josch

2
@josch: Bei der Frage geht es nicht um das Auflösen von Symlinks. Aber wenn Sie das tun möchten, können Sie die -POption zum pwdBefehl bereitstellen :echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Eugen Konkov

4
Ich mag die Antwort, aber es funktioniert nur, wenn der Benutzer in das Verzeichnis cd darf. Dies ist möglicherweise nicht immer möglich.
Matthias B

34

Für das, was es wert ist, habe ich für die Antwort gestimmt, die ausgewählt wurde, wollte aber eine Lösung teilen. Der Nachteil ist, dass es nur Linux ist - ich habe ungefähr 5 Minuten lang versucht, das OSX-Äquivalent zu finden, bevor ich zum Stack-Überlauf kam. Ich bin mir aber sicher, dass es da draußen ist.

Unter Linux können Sie readlink -ezusammen mit verwenden dirname.

$(dirname $(readlink -e ../../../../etc/passwd))

ergibt

/etc/

Und dann benutzt du die dirnameSchwester, basenameum nur den Dateinamen zu bekommen

$(basename ../../../../../passwd)

ergibt

passwd

Alles zusammen ..

F=../../../../../etc/passwd
echo "$(dirname $(readlink -e $F))/$(basename $F)"

ergibt

/etc/passwd

Sie sind sicher, wenn Sie auf ein Verzeichnis abzielen, basenamenichts zurückgeben und in der endgültigen Ausgabe nur doppelte Schrägstriche erhalten.


Ausgezeichnete Eintrag mit dirname, readlinkund basename. Das hat mir geholfen, den absoluten Weg einer symbolischen Verbindung zu finden - nicht ihr Ziel.
Kevinarpe

Funktioniert nicht, wenn Sie den Pfad zu symbolischen Links zurückgeben möchten (was ich zufällig tun muss ...).
Tomáš Zato - Wiedereinsetzung Monica

Wie könnten Sie jemals den absoluten Weg zu einem Weg finden, den es nicht gibt?
Synthesizerpatel

@synthesizerpatel Ganz einfach, hätte ich gedacht; wenn ich in bin /home/GKFXund ich gebe touch newfile, dann , bevor ich die Eingabetaste drücken könnten Sie arbeiten heraus , dass ich meine „create / home / GKFX / newfile“, die ein absoluter Pfad zu einer Datei, die noch nicht existiert.
GKFX

25

Ich denke, das ist das tragbarste:

abspath() {                                               
    cd "$(dirname "$1")"
    printf "%s/%s\n" "$(pwd)" "$(basename "$1")"
    cd "$OLDPWD"
}

Es schlägt jedoch fehl, wenn der Pfad nicht vorhanden ist.


3
Es ist nicht nötig, erneut eine CD zu erstellen. Siehe stackoverflow.com/a/21188136/1504556 . Ihre ist die beste Antwort auf dieser Seite, IMHO. Für Interessenten gibt der Link eine Erklärung, warum diese Lösung funktioniert.
Peterh

Dies ist nicht sehr portabel, dirnameist ein GNU-Kerndienstprogramm, das meiner Meinung nach nicht allen Unixen gemeinsam ist.
einpoklum

@einpoklum dirnameist ein POSIX-Standarddienstprogramm, siehe hier: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A.

Oh mein Gott, danke. Ich habe versucht, die Version zu reparieren, die ${1##*/}seit einem Tag verwendet wird, und jetzt, wo ich diesen Papierkorb durch basename "$1"ihn ersetzt habe, scheint es endlich richtig zu sein, Pfade zu behandeln, die mit / enden.
l3l_aze

NB, das macht nicht das Richtige mit einem Pfad, der in endet../..
Alex Coventry

16

realpath ist wahrscheinlich am besten

Aber ...

Die anfängliche Frage war anfangs sehr verwirrt, mit einem Beispiel, das, wie angegeben, schlecht mit der Frage zusammenhängt.

Die ausgewählte Antwort beantwortet tatsächlich das gegebene Beispiel und überhaupt nicht die Frage im Titel. Der erste Befehl ist diese Antwort (ist es wirklich? Ich bezweifle) und könnte auch ohne das '/' auskommen. Und ich sehe nicht, was der zweite Befehl tut.

Mehrere Themen sind gemischt:

  • Ändern eines relativen Pfadnamens in einen absoluten, was auch immer es bedeutet, möglicherweise nichts. (In der Regel, wenn Sie einen Befehl wie ztouch foo/bar , foo/barmuss der Pfadname für Sie vorhanden sein und möglicherweise für die Berechnung verwendet werden, bevor die Datei tatsächlich erstellt wird. )

  • Es kann mehrere absolute Pfadnamen geben, die dieselbe Datei (oder potenzielle Datei) bezeichnen, insbesondere aufgrund symbolischer Links (Symlinks) auf dem Pfad, möglicherweise jedoch aus anderen Gründen (ein Gerät kann doppelt als schreibgeschützt bereitgestellt werden). Man kann solche Symlinks explizit auflösen oder nicht.

  • Erreichen des Endes einer Kette symbolischer Links zu einer Datei oder einem Namen ohne Symlink. Dies kann einen absoluten Pfadnamen ergeben oder nicht, abhängig davon, wie es gemacht wird. Und man kann es in einen absoluten Pfadnamen auflösen oder nicht.

Der Befehl readlink foo ohne Option gibt nur dann eine Antwort, wenn sein Argument fooein symbolischer Link ist und diese Antwort der Wert dieses Symlinks ist. Es wird kein anderer Link verfolgt. Die Antwort kann ein relativer Pfad sein: Was auch immer der Wert des Symlink-Arguments war.

Jedoch, readlinkVerfügt über Optionen (-f -e oder -m), die für alle Dateien funktionieren, und gibt der Datei, die tatsächlich durch das Argument gekennzeichnet ist, einen absoluten Pfadnamen (den ohne Symlinks).

Dies funktioniert gut für alles, was kein Symlink ist, obwohl man möglicherweise einen absoluten Pfadnamen verwenden möchte, ohne die Zwischensymlinks auf dem Pfad aufzulösen. Dies erfolgt durch den Befehlrealpath -s foo

Im Fall eines Symlink-Arguments werden readlinkmit seinen Optionen wieder alle Symlinks auf dem absoluten Pfad zum Argument aufgelöst, aber dies schließt auch alle Symlinks ein, die auftreten können, wenn Sie dem Argumentwert folgen. Möglicherweise möchten Sie dies nicht, wenn Sie einen absoluten Pfad zum Argument symlink selbst wünschen, und nicht zu dem, mit dem es verknüpft ist. Auch wenn if fooein Symlink ist,realpath -s foo wird ein absoluter Pfad ohne Auflösen von Symlinks erhalten, einschließlich des als Argument angegebenen.

Ohne die -sOption realpathfunktioniert dies ziemlich genau so readlink, außer dass nur der Wert eines Links und einige andere Dinge gelesen werden. Mir ist einfach nicht klar, warum readlinkes seine Optionen gibt, was anscheinend zu einer unerwünschten Redundanz führt realpath .

Das Erkunden des Webs sagt nicht viel mehr aus, außer dass es einige Unterschiede zwischen den Systemen geben kann.

Fazit: realpathist der beste Befehl mit der größten Flexibilität, zumindest für die hier angeforderte Verwendung.


10

Meine Lieblingslösung war die von @EugenKonkov, da dies nicht die Anwesenheit anderer Dienstprogramme (das Coreutils-Paket) implizierte.

Aber es ist für die relativen Pfade "fehlgeschlagen." und "..", daher hier eine leicht verbesserte Version, die diese Sonderfälle behandelt.

Es schlägt jedoch immer noch fehl, wenn der Benutzer nicht über die Berechtigung verfügt, cdin das übergeordnete Verzeichnis des relativen Pfads zu wechseln.

#! /bin/sh

# Takes a path argument and returns it as an absolute path. 
# No-op if the path is already absolute.
function to-abs-path {
    local target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

7

Eugens Antwort hat bei mir nicht ganz funktioniert, aber das hat funktioniert:

    absolute="$(cd $(dirname \"$file\"); pwd)/$(basename \"$file\")"

Nebenbei bemerkt, Ihr aktuelles Arbeitsverzeichnis ist nicht betroffen.


Hinweis: Das ist ein Skript. wo $ 1 das erste Argument dafür ist
Eugen Konkov

5

Die beste Lösung, imho, ist die hier veröffentlichte: https://stackoverflow.com/a/3373298/9724628 .

Es erfordert Python, um zu funktionieren, aber es scheint alle oder die meisten Randfälle abzudecken und eine sehr tragbare Lösung zu sein.

  1. Mit dem Auflösen von Symlinks:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
  1. oder ohne:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

3

Im Falle von findist es wahrscheinlich am einfachsten, nur den absoluten Pfad für die Suche anzugeben, z.

find /etc
find `pwd`/subdir_of_current_dir/ -type f

3

Wenn Sie Bash unter Mac OS X verwenden, bei dem weder ein Realpath vorhanden ist noch der Readlink den absoluten Pfad drucken kann, haben Sie möglicherweise die Wahl, Ihre eigene Version zu codieren, um ihn zu drucken. Hier ist meine Implementierung:

(reine Bash)

abspath(){
  local thePath
  if [[ ! "$1" =~ ^/ ]];then
    thePath="$PWD/$1"
  else
    thePath="$1"
  fi
  echo "$thePath"|(
  IFS=/
  read -a parr
  declare -a outp
  for i in "${parr[@]}";do
    case "$i" in
    ''|.) continue ;;
    ..)
      len=${#outp[@]}
      if ((len==0));then
        continue
      else
        unset outp[$((len-1))] 
      fi
      ;;
    *)
      len=${#outp[@]}
      outp[$len]="$i"
      ;;
    esac
  done
  echo /"${outp[*]}"
)
}

(benutze gawk)

abspath_gawk() {
    if [[ -n "$1" ]];then
        echo $1|gawk '{
            if(substr($0,1,1) != "/"){
                path = ENVIRON["PWD"]"/"$0
            } else path = $0
            split(path, a, "/")
            n = asorti(a, b,"@ind_num_asc")
            for(i in a){
                if(a[i]=="" || a[i]=="."){
                    delete a[i]
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            m = 0
            while(m!=n){
                m = n
                for(i=1;i<=n;i++){
                    if(a[b[i]]==".."){
                        if(b[i-1] in a){
                            delete a[b[i-1]]
                            delete a[b[i]]
                            n = asorti(a, b, "@ind_num_asc")
                            break
                        } else exit 1
                    }
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            if(n==0){
                printf "/"
            } else {
                for(i=1;i<=n;i++){
                    printf "/"a[b[i]]
                }
            }
        }'
    fi
}

(pure bsd awk)

#!/usr/bin/env awk -f
function abspath(path,    i,j,n,a,b,back,out){
  if(substr(path,1,1) != "/"){
    path = ENVIRON["PWD"]"/"path
  }
  split(path, a, "/")
  n = length(a)
  for(i=1;i<=n;i++){
    if(a[i]==""||a[i]=="."){
      continue
    }
    a[++j]=a[i]
  }
  for(i=j+1;i<=n;i++){
    delete a[i]
  }
  j=0
  for(i=length(a);i>=1;i--){
    if(back==0){
      if(a[i]==".."){
        back++
        continue
      } else {
        b[++j]=a[i]
      }
    } else {
      if(a[i]==".."){
        back++
        continue
      } else {
        back--
        continue
      }
    }
  }
  if(length(b)==0){
    return "/"
  } else {
    for(i=length(b);i>=1;i--){
      out=out"/"b[i]
    }
    return out
  }
}

BEGIN{
  if(ARGC>1){
    for(k=1;k<ARGC;k++){
      print abspath(ARGV[k])
    }
    exit
  }
}
{
  print abspath($0)
}

Beispiel:

$ abspath I/am/.//..//the/./god/../of///.././war
/Users/leon/I/the/war

1

Was sie sagten, außer find $PWDoder (in Bash) find ~+ist ein bisschen bequemer.


1

Ähnlich wie bei der Antwort von @ ernest-a, aber ohne $OLDPWDeine neue Funktion zu beeinflussen oder zu definieren, können Sie eine Subshell auslösen(cd <path>; pwd)

$ pwd
/etc/apache2
$ cd ../cups 
$ cd -
/etc/apache2
$ (cd ~/..; pwd)
/Users
$ cd -
/etc/cups

1

Wenn der relative Pfad ein Verzeichnispfad ist, versuchen Sie meinen, sollte der beste sein:

absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)

echo $absPath

Diese Lösung funktioniert nur für Bash, siehe auch stackoverflow.com/a/5193087/712014
Michael

1
echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'

3
Vielen Dank für dieses Code-Snippet, das möglicherweise nur begrenzte kurzfristige Hilfe bietet. Eine richtige Erklärung würde ihren langfristigen Wert erheblich verbessern, indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und es für zukünftige Leser mit anderen, ähnlichen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, einschließlich der von Ihnen getroffenen Annahmen.
Toby Speight

1

Eine Verbesserung der ziemlich schönen Version von @ ernest-a:

absolute_path() {
    cd "$(dirname "$1")"
    case $(basename $1) in
        ..) echo "$(dirname $(pwd))";;
        .)  echo "$(pwd)";;
        *)  echo "$(pwd)/$(basename $1)";;
    esac
}

Dies behandelt den Fall korrekt, in dem das letzte Element des Pfades ist ... In diesem Fall wird die "$(pwd)/$(basename "$1")"Antwort von in @ ernest-a als durchkommen accurate_sub_path/spurious_subdirectory/...


0

Wenn Sie eine Variable mit einem relativen Pfad in einen absoluten Pfad umwandeln möchten, funktioniert dies wie folgt:

   dir=`cd "$dir"`

"cd" hallt wider, ohne das Arbeitsverzeichnis zu ändern, da es hier in einer Sub-Shell ausgeführt wird.


2
Auf bash-4.3-p46 funktioniert dies nicht: Die Shell druckt eine leere Zeile, wenn ich laufedir=`cd ".."` && echo $dir
Michael

0

Dies ist eine verkettete Lösung von allen anderen, z. B. wenn ein realpathFehler auftritt, entweder weil sie nicht installiert ist oder weil sie mit Fehlercode beendet wird, wird die nächste Lösung versucht, bis der richtige Pfad gefunden wurde.

#!/bin/bash

function getabsolutepath() {
    local target;
    local changedir;
    local basedir;
    local firstattempt;

    target="${1}";
    if [ "$target" == "." ];
    then
        printf "%s" "$(pwd)";

    elif [ "$target" == ".." ];
    then
        printf "%s" "$(dirname "$(pwd)")";

    else
        changedir="$(dirname "${target}")" && basedir="$(basename "${target}")" && firstattempt="$(cd "${changedir}" && pwd)" && printf "%s/%s" "${firstattempt}" "${basedir}" && return 0;
        firstattempt="$(readlink -f "${target}")" && printf "%s" "${firstattempt}" && return 0;
        firstattempt="$(realpath "${target}")" && printf "%s" "${firstattempt}" && return 0;

        # If everything fails... TRHOW PYTHON ON IT!!!
        local fullpath;
        local pythoninterpreter;
        local pythonexecutables;
        local pythonlocations;

        pythoninterpreter="python";
        declare -a pythonlocations=("/usr/bin" "/bin");
        declare -a pythonexecutables=("python" "python2" "python3");

        for path in "${pythonlocations[@]}";
        do
            for executable in "${pythonexecutables[@]}";
            do
                fullpath="${path}/${executable}";

                if [[ -f "${fullpath}" ]];
                then
                    # printf "Found ${fullpath}\\n";
                    pythoninterpreter="${fullpath}";
                    break;
                fi;
            done;

            if [[ "${pythoninterpreter}" != "python" ]];
            then
                # printf "Breaking... ${pythoninterpreter}\\n"
                break;
            fi;
        done;

        firstattempt="$(${pythoninterpreter} -c "import os, sys; print( os.path.abspath( sys.argv[1] ) );" "${target}")" && printf "%s" "${firstattempt}" && return 0;
        # printf "Error: Could not determine the absolute path!\\n";
        return 1;
    fi
}

printf "\\nResults:\\n%s\\nExit: %s\\n" "$(getabsolutepath "./asdfasdf/ asdfasdf")" "${?}"

0

Sie können die Bash-String-Ersetzung für jeden relativen Pfad $ line verwenden:

line=$(echo ${line/#..\//`cd ..; pwd`\/})
line=$(echo ${line/#.\//`pwd`\/})
echo $line

Die grundlegende Front-of-String-Ersetzung folgt der Formel
$ {string / # substring / replace},
die hier ausführlich erläutert wird: https://www.tldp.org/LDP/abs/html/string-manipulation.html

Das \Zeichen negiert das, /wenn es Teil der Zeichenfolge sein soll, die wir finden / ersetzen.


0

Ich konnte keine Lösung finden, die zwischen Mac OS Catalina, Ubuntu 16 und Centos 7 problemlos portierbar war. Daher habe ich mich für Python Inline entschieden und es funktionierte gut für meine Bash-Skripte.

to_abs_path() {
  python -c "import os; print os.path.abspath('$1')"
}

to_abs_path "/some_path/../secrets"
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.