Erweitern Sie einen möglichen relativen Pfad in Bash


82

Als Argumente für mein Skript gibt es einige Dateipfade. Diese können natürlich relativ sein (oder ~ enthalten). Aber für die Funktionen, die ich geschrieben habe, brauche ich Pfade, die absolut sind, deren Symlinks jedoch nicht aufgelöst sind.

Gibt es dafür eine Funktion?



7
readlinklöst Symlinks auf; Das OP will das nicht.
Keith Thompson



1
echo $(cd some_directory && pwd), Symlink nicht auflösen, schlägt fehl, wenn some_directory nicht vorhanden ist. Arbeitsverzeichnis nicht betroffen. oder einer Variablen zuweisen MY_PATH=$(cd some_directory && pwd).
Qeatzy

Antworten:


79

MY_PATH=$(readlink -f $YOUR_ARG)löst relative Pfade wie "./"und auf"../"

Beachten Sie auch dies ( Quelle ):

#!/bin/bash
dir_resolve()
{
cd "$1" 2>/dev/null || return $?  # cd to desired directory; if fail, quell any error messages but return exit status
echo "`pwd -P`" # output full, link-resolved path
}

# sample usage
if abs_path="`dir_resolve \"$1\"`"
then
echo "$1 resolves to $abs_path"
echo pwd: `pwd` # function forks subshell, so working directory outside function is not affected
else
echo "Could not reach $1"
fi

Das ist sicherlich schön, aber das Problem ist, dass Simlinks gelöst werden und dies zu Verwirrung bei den Benutzern führen würde.
Laar

man pwd: "-P Zeigt das physische aktuelle Arbeitsverzeichnis an (alle symbolischen Links wurden aufgelöst)", entfernen Sie einfach das-P
andsens

7
Um die readlink -fFunktionalität unter OS X zu erhalten, können Sie Homebrew verwenden und dann ausführen: $ brew install coreutils Sie können dann verwenden greadlink -f.
jgpawletko

45

http://www.linuxquestions.org/questions/programming-9/bash-script-return-full-path-and-filename-680368/page3.html hat Folgendes

function abspath {
    if [[ -d "$1" ]]
    then
        pushd "$1" >/dev/null
        pwd
        popd >/dev/null
    elif [[ -e "$1" ]]
    then
        pushd "$(dirname "$1")" >/dev/null
        echo "$(pwd)/$(basename "$1")"
        popd >/dev/null
    else
        echo "$1" does not exist! >&2
        return 127
    fi
}

welches pushd/ benutzt popd, um in einen Zustand zu gelangen, in dem pwdes nützlich ist.


2
Leider ist dies wirklich die beste Antwort hier und es wird nicht abgestimmt, wo es hingehört. +1 für die genaue Beantwortung der Frage des OP. Er möchte, dass Symlinks nicht gelöst werden, was wirklich eine Frage von Bashs Lokalität ist DIRSTACKund sonst nichts. Schöne Lösung!
cptstubing06

5
Wenn Standardkompatibilität angestrebt wird sh, pushd; cd <dir>; <cmd>; popdkann durch ersetzt werden (cd <dir>; <cmd>).
Abbafei

16

Einfacher Einzeiler:

function abs_path {
  (cd "$(dirname '$1')" &>/dev/null && printf "%s/%s" "$PWD" "${1##*/}")
}

Verwendung:

function do_something {
    local file=$(abs_path $1)
    printf "Absolute path to %s: %s\n" "$1" "$file"
}
do_something $HOME/path/to/some\ where

Ich versuche immer noch herauszufinden, wie ich dafür sorgen kann, dass ich nicht weiß, ob der Pfad existiert oder nicht (daher kann er auch beim Erstellen von Dateien verwendet werden).


1
Wird dadurch nicht das aktuelle Arbeitsverzeichnis geändert? Soll ich es cd -später zurücksetzen?
Devios1

4
Das ist nicht nötig. Beachten Sie die Parens, sie bewirken, dass die darin enthaltenen Befehle in einer Subshell ausgeführt werden. Der Status (z. B. das Arbeitsverzeichnis) einer Subshell geht nicht in die übergeordnete Shell über. Lesen Sie hier mehr: tldp.org/LDP/abs/html/subshells.html
andsens

1
Schöner Einzeiler. Es gibt jedoch ein Problem: Wenn der erweiterte Wert von $ 1 keinen Schrägstrich enthält (dh es handelt sich um einen Dateinamen im aktuellen Verzeichnis), wird $ {1% / *} auf den Dateinamen selbst erweitert und der Befehl cd schlägt fehl. Möglicherweise möchten Sie stattdessen $ (dirname $ 1) verwenden, das auf '.' Erweitert wird. In diesem Fall.
Paulo

Du hast recht, aktualisiert. Ich habe einige zitiert Probleme mit dem Ersetzen ${1##*/}mit basenameallerdings ohne es in einer separaten Variable zu setzen, Pfade mit Leerzeichen nicht richtig funktionieren.
andsens

1
@BryanP Am Ende bin ich für den Dotfile-Synchronizer, den ich pflege, völlig über Bord gegangen. Es verfügt über eine Funktion clean_path (), mit der nicht benötigte Pfadteile entfernt werden. Wenn Sie einen relativen Pfad am Ende des $PWD
Pfads festhalten

8

Dies macht den Trick für mich unter OS X: $(cd SOME_DIRECTORY 2> /dev/null && pwd -P)

Es sollte überall funktionieren. Die anderen Lösungen schienen zu kompliziert.


4

unter OS X können Sie verwenden

stat -f "%N" YOUR_PATH

Unter Linux haben Sie möglicherweise eine realpathausführbare Datei. Andernfalls funktioniert möglicherweise Folgendes (nicht nur für Links):

readlink -c YOUR_PATH

20
Die OS X-Antwort funktioniert nicht. stat -f "%N" PATHgibt mir genau den gleichen Weg, den ich ihm gegeben habe.
Lily Ballard

3

Verwenden Sie readlink -f <relative-path>z

export FULLPATH=`readlink -f ./`

2

Möglicherweise ist dies besser lesbar und verwendet keine Unterschale und ändert das aktuelle Verzeichnis nicht:

dir_resolve() {
  local dir=`dirname "$1"`
  local file=`basename "$1"`
  pushd "$dir" &>/dev/null || return $? # On error, return error code
  echo "`pwd -P`/$file" # output full, link-resolved path with filename
  popd &> /dev/null
}

1

Es gibt noch eine andere Methode. Sie können die Python-Einbettung in ein Bash-Skript verwenden, um einen relativen Pfad aufzulösen.

abs_path=$(python3 - <<END
from pathlib import Path
path = str(Path("$1").expanduser().resolve())
print(path)
END
)

0

Selbstbearbeitung, ich habe gerade bemerkt, dass das OP sagte, er suche nicht nach gelösten Symlinks:

"Aber für die Funktionen, die ich geschrieben habe, brauche ich Pfade, die absolut sind, deren Symlinks jedoch nicht aufgelöst sind."

Vermutlich ist das doch nicht so passend für seine Frage. :) :)

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 könnte 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

0

Wenn Ihr Betriebssystem dies unterstützt, verwenden Sie:

realpath "./some/dir"

Und mit einer Variablen:

some_path="$(realpath "./some/dir")"

Welches wird Ihren Weg erweitern. Unter Ubuntu und CentOS getestet, ist es bei Ihnen möglicherweise nicht verfügbar. Einige empfehlen Readlink, aber die Dokumentation für Readlink lautet:

Beachten Sie, dass realpath (1) der bevorzugte Befehl für die Kanonisierungsfunktionalität ist.

Falls sich die Leute fragen, warum ich meine Variablen zitiere, müssen Leerzeichen in Pfaden erhalten bleiben. Wenn Sie dies tun realpath some path, erhalten Sie zwei unterschiedliche Pfadergebnisse. Aber realpath "some path"ich werde einen zurückgeben. Zitierte Parameter ftw :)


-1

Müssen Sie ausschließlich Bash verwenden? Ich musste dies tun und hatte genug von Unterschieden zwischen Linux und OS X. Also benutzte ich PHP für eine schnelle und schmutzige Lösung.

#!/usr/bin/php <-- or wherever
<?php
{
   if($argc!=2)
      exit();
   $fname=$argv[1];
   if(!file_exists($fname))
      exit();
   echo realpath($fname)."\n";
}
?>

Ich weiß, dass es keine sehr elegante Lösung ist, aber es funktioniert.


Dies bedeutet nichts anderes, als den Pfad zur Datei zurückzugeben, wenn Sie bereits wissen, dass der Pfad zur Datei vorhanden ist oder sich die Datei im aktuellen Verzeichnis befindet. Die Datei wird in den PATH-Einstellungen nicht gefunden
G-Man
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.