Zuverlässige Methode für ein Bash-Skript, um den vollständigen Pfad zu sich selbst zu erhalten?

Ich habe ein Bash-Skript, das seinen vollständigen Pfad kennen muss. Ich versuche, eine weitgehend kompatible Methode zu finden, ohne dabei auf relative oder funky aussehende Pfade zu stoßen. Ich muss nur bash unterstützen, nicht sh, csh usw.

Was ich bisher gefunden habe:

  1. Die akzeptierte Antwort auf ” Das Quellverzeichnis eines Bash-Skripts von innen holen” adressiert den Pfad des Skripts über dirname $0 , was in Ordnung ist, aber das kann einen relativen Pfad zurückgeben (like . ), Was ein Problem ist, wenn Sie wollen Um Verzeichnisse im Skript zu ändern, muss der Pfad immer noch auf das Verzeichnis des Skripts zeigen. Trotzdem wird dirname Teil des Puzzles sein.

  2. Die akzeptierte Antwort auf ” Absoluter Bash-Skript-Pfad mit OSX ” (OS X spezifisch, aber die Antwort funktioniert unabhängig) gibt eine function, die prüft, ob $0 relativ aussieht und wenn ja, wird $PWD an sie $PWD . Aber das Ergebnis kann immer noch relative Bits enthalten (obwohl es insgesamt absolut ist) – zum Beispiel, wenn das Skript t im Verzeichnis /usr/bin und Sie sich in /usr und Sie bin/../bin/t /usr/bin/../bin es auszuführen (ja, das ist gewunden), enden Sie mit /usr/bin/../bin als /usr/bin/../bin des Skripts. Was funktioniert , aber …

  3. Die readlink Lösung auf dieser Seite , die so aussieht:

     # Absolute path to this script. /home/user/bin/foo.sh SCRIPT=$(readlink -f $0) # Absolute path this script is in. /home/user/bin SCRIPTPATH=`dirname $SCRIPT` 

    Aber readlink ist nicht POSIX und anscheinend readlink die Lösung auf GNUs readlink wo readlink aus irgendeinem Grund nicht funktionieren (ich habe keinen Zugang zu einem BSD-ähnlichen System zu überprüfen).

Also, verschiedene Möglichkeiten, es zu tun, aber alle haben ihre Vorbehalte.

Was wäre ein besserer Weg? Wo “besser” heißt:

  • Gibt mir den absoluten Weg.
  • Entfernt funky Bits, selbst wenn sie in einer verschachtelten Weise aufgerufen werden (siehe Kommentar zu # 2 oben). (ZB wird der Pfad zumindest moderat kanonisiert.)
  • Basiert nur auf Bash-Ismen oder Dingen, die fast sicher zu den beliebtesten Varianten von * nix-Systemen gehören (GNU / Linux, BSD und BSD-ähnliche Systeme wie OS X usw.).
  • Vermeidet das Aufrufen von externen Programmen, wenn möglich (zB bevorzugt bash built-ins).
  • ( Aktualisiert , danke für die heads up, wich ) Muss nicht lösen Symlinks (in der Tat, ich würde es bevorzugen, es ließ sie in Ruhe, aber das ist keine Voraussetzung).

Hier ist, was ich mir ausgedacht habe (Schnitt: plus ein paar Verbesserungen von sfstewman , levigroker , Kyle Strand und Rob Kennedy ), die meistens meinen “besseren” Kriterien entsprechen:

 SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 

Diese SCRIPTPATH Zeile scheint besonders SCRIPTPATH , aber wir brauchen sie lieber als SCRIPTPATH=`pwd` , um Leerzeichen und SCRIPTPATH=`pwd` richtig zu behandeln.

Beachten Sie auch, dass esoterische Situationen, wie das Ausführen eines Skripts, das nicht aus einer Datei in einem zugänglichen Dateisystem kommt (was durchaus möglich ist), dort nicht behandelt werden (oder in einer der anderen Antworten, die ich gesehen habe) ).

Ich bin überrascht, dass der Befehl realpath hier nicht erwähnt wurde. Mein Verständnis ist, dass es weit tragbar / portiert ist.

Ihre anfängliche Lösung wird:

 SCRIPT=`realpath $0` SCRIPTPATH=`dirname $SCRIPT` 

Und um symbolische Links nach Ihren Präferenzen ungetriggers zu lassen:

 SCRIPT=`realpath -s $0` SCRIPTPATH=`dirname $SCRIPT` 

Der einfachste Weg, den ich gefunden habe, um einen vollständigen kanonischen Pfad in bash zu erhalten, ist cd und pwd :

 ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" 

Die Verwendung von ${BASH_SOURCE[0]} anstelle von $0 erzeugt das gleiche Verhalten, unabhängig davon, ob das Skript als oder als source aufgerufen wird

Ich musste dieses Problem heute noch einmal untersuchen und fand https://Stackoverflow.com/a/246128/1034080 . Es geht um eine Lösung, die ich auch in der Vergangenheit verwendet habe .

 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 

Es gibt mehr Varianten in der verknüpften Antwort, z. B. für den Fall, dass das Skript selbst ein Symlink ist.

Absoluter Pfad des Shell-Skripts

Verwendet die Option -f nicht in readlink, sollte daher in bsd / mac-osx funktionieren

Unterstützt

  • Quelle ./script (Wenn vom . Punktoperator aufgerufen)
  • Absoluter Pfad / Pfad / zu / Skript
  • Relativer Pfad wie ./script
  • /path/dir1/../dir2/dir3/../script
  • Wenn vom Symlink aufgerufen
  • Wenn symlink verschachtelt ist zB) foo->dir1/dir2/bar bar->./../doe doe->script
  • Wenn der Aufrufer den Namen des Skripts ändert

Ich suche nach Fällen in denen dieser Code nicht funktioniert . Lass es mich wissen, bitte.

Code

 pushd . > /dev/null SCRIPT_PATH="${BASH_SOURCE[0]}"; while([ -h "${SCRIPT_PATH}" ]); do cd "`dirname "${SCRIPT_PATH}"`" SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")"; done cd "`dirname "${SCRIPT_PATH}"`" > /dev/null SCRIPT_PATH="`pwd`"; popd > /dev/null echo "srcipt=[${SCRIPT_PATH}]" echo "pwd =[`pwd`]" 

Bekannte Probleme

Das Skript muss irgendwo auf der Festplatte sein , lass es über ein Netzwerk laufen. Wenn Sie versuchen, dieses Skript von einem PIPE auszuführen, wird es nicht funktionieren

 wget -o /dev/null -O - http://host.domain/dir/script.sh |bash 

Technisch gesehen ist es undefiniert.
In der Praxis gibt es keinen vernünftigen Weg, dies zu erkennen. (Co-process kann nicht auf env des Elternteils zugreifen)

Was ist mit der Verwendung von:

 SCRIPT_PATH=$(dirname `which $0`) 

which ausgibt, um den vollständigen Pfad der ausführbaren Datei zu stdooten, der ausgeführt würde, als das übergebene Argument an der Shell-Eingabeaufforderung eingegeben wurde (was $ 0 enthält)

dirname das Nicht-Verzeichnis-Suffix aus dem Dateinamen

Daher haben Sie den vollständigen Pfad zum Skript, unabhängig davon, ob der Pfad angegeben wurde oder nicht.

Da realpath nicht standardmäßig auf meinem Linux System installiert ist, funktioniert folgendes für mich:

 SCRIPT="$(readlink --canonicalize-existing "$0")" SCRIPTPATH="$(dirname "$SCRIPT")" 

$SCRIPT enthält den echten Dateipfad zum Skript und $SCRIPTPATH den tatsächlichen Pfad des Verzeichnisses, das das Skript enthält.

Bevor Sie dies lesen, lesen Sie die Kommentare dieser Antwort .

Ich beantworte diese Frage sehr spät, aber ich benutze:

 SCRIPT=$( readlink -m $( type -p $0 )) # Full path to script BASE_DIR=`dirname ${SCRIPT}` # Directory script is run in NAME=`basename ${SCRIPT}` # Actual name of script even if linked 

Wir haben unser eigenes Produkt realpath-lib auf GitHub für freie und unbelastete Community-Nutzung platziert.

Schamloser Stecker, aber mit dieser Bash-Bibliothek können Sie:

 get_realpath  

Diese function ist der core 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 benötigt keine externen Abhängigkeiten, nur Bash 4+. Enthält auch functionen zu get_dirname , get_filename , get_stemname und validiere_pfad validate_realpath . Es ist kostenlos, sauber, einfach und gut dokumentiert, so dass es auch für Lernzwecke verwendet werden kann und ohne Zweifel verbessert werden kann. Probieren Sie es plattformübergreifend aus.

Update: Nach einigen Überprüfungen und Tests haben wir die obige function durch etwas ersetzt, das das gleiche Ergebnis erzielt (ohne dirname, nur pure Bash), aber mit besserer Effizienz:

 function get_realpath() { [[ ! -f "$1" ]] && return 1 # failure : file does not exist. [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks. echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result. return 0 # success } 

Dies umfasst auch eine Umgebungseinstellung no_symlinks , die die Fähigkeit zum no_symlinks Symlinks zum physischen System bietet. Standardmäßig hält es symbolische Links intakt.

Sie können versuchen, die folgende Variable zu definieren:

 CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 

oder Sie können die folgende function in bash versuchen:

 realpath () { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" } 

Diese function benötigt 1 Argument. Wenn das Argument bereits einen absoluten Pfad hat, drucke es so, wie es ist, andernfalls drucke $PWD Variable + Dateiname Argument (ohne ./ Präfix).

Verbunden:

  • Absoluter Bash-Skriptpfad mit OSX

  • Das Quellverzeichnis eines Bash-Skripts von innen holen

sh konformen Weg:

 SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done` 

einfach: BASEDIR=$(readlink -f $0 | xargs dirname)

keine ausgefallenen Betreiber

Hallo.

In Anbetracht dieses Problems erneut: Es gibt eine sehr beliebte Lösung, auf die in diesem Thread verwiesen wird, die hier ihren Ursprung hat :

 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 

Ich bin von dieser Lösung wegen der Verwendung von dirname weggeblieben – es kann plattformübergreifende Schwierigkeiten darstellen, insbesondere wenn ein Skript aus Sicherheitsgründen gesperrt werden muss. Aber als reine Bash-Alternative, wie wäre es mit:

 DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )" 

Wäre das eine Option?

Vielleicht kann die akzeptierte Antwort auf die folgende Frage hilfreich sein.

Wie bekomme ich das Verhalten von GNU’s Readlink -f auf einem Mac?

Vorausgesetzt, dass Sie nur den Namen abs_dir=${abs_dir//\/.\//\/} möchten, den Sie durch Verketten von $ PWD und $ 0 erhalten (vorausgesetzt, dass $ 0 nicht absolut ist), verwenden Sie einfach eine Reihe von Regex-Ersetzungen entlang der Zeile abs_dir=${abs_dir//\/.\//\/} und so.

Ja, ich weiß, es sieht schrecklich aus, aber es wird funktionieren und ist pure Bash.

Die akzeptierte Lösung hat die unangenehme (für mich) nicht “quellfähig” zu sein:
Wenn Sie es von einem ” source ../../yourScriptsource ../../yourScript , wäre $0bash “!

Die folgende function (für bash> = 3.0) gibt mir den richtigen Pfad, aber das Skript könnte aufgerufen werden (direkt oder über die source , mit einem absoluten oder relativen Pfad):
(mit “rechter Pfad”, ich meine den vollständigen absoluten Pfad des aufgerufenen Skripts , auch wenn von einem anderen Pfad direkt oder mit ” source ” aufgerufen)

 #!/bin/bash echo $0 executed function bashscriptpath() { local _sp=$1 local ascript="$0" local asp="$(dirname $0)" #echo "b1 asp '$asp', b1 ascript '$ascript'" if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}" elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd) else if [[ "$ascript" == "bash" ]] ; then ascript=${BASH_SOURCE[0]} asp="$(dirname $ascript)" fi #echo "b2 asp '$asp', b2 ascript '$ascript'" if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ; elif [[ "${ascript#../}" != "$ascript" ]]; then asp=$(pwd) while [[ "${ascript#../}" != "$ascript" ]]; do asp=${asp%/*} ascript=${ascript#../} done elif [[ "${ascript#*/}" != "$ascript" ]]; then if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi fi fi eval $_sp="'$asp'" } bashscriptpath H export H=${H} 

Der Schlüssel besteht darin, den Fall ” source ” zu erkennen und ${BASH_SOURCE[0]} zu verwenden, um das eigentliche Skript zurückzubekommen.

Wenn wir Bash verwenden, glaube ich, dass dies der bequemste Weg ist, da es keine Aufrufe von externen Befehlen erfordert:

 THIS_PATH="${BASH_SOURCE[0]}"; THIS_DIR=$(dirname $THIS_PATH) 

Leicht zu lesen? Alternative. Ignoriert Symlinks

 #!/bin/bash currentDir=$( cd $(dirname "$0") pwd ) echo -n "current " pwd echo script $currentDir 

Versuche dies:

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

Nur zum Teufel habe ich ein bisschen an einem Skript gehackt, das Dinge rein textlich macht, rein in bash. Ich hoffe, ich habe alle Randfälle erfasst. Beachten Sie, dass das ${var//pat/repl} , das ich in der anderen Antwort erwähnte, nicht funktioniert, da Sie nicht nur die kürzest mögliche Übereinstimmung ersetzen können, was ein Problem beim Ersetzen von /foo/../ zB /*/../ nimmt alles davor und nicht nur einen einzigen Eintrag. Und da diese Muster keine echten Regexes sind, sehe ich nicht, wie das funktionieren kann. Also hier ist die schön gewundene Lösung, die ich mir ausgedacht habe, genießen. 😉

Übrigens, lassen Sie es mich wissen, wenn Sie unbearbeitete Randfälle finden.

 #!/bin/bash canonicalize_path() { local path="$1" OIFS="$IFS" IFS=$'/' read -a parts < <(echo "$path") IFS="$OIFS" local i=${#parts[@]} local j=0 local back=0 local -a rev_canon while (($i > 0)); do ((i--)) case "${parts[$i]}" in ""|.) ;; ..) ((back++));; *) if (($back > 0)); then ((back--)) else rev_canon[j]="${parts[$i]}" ((j++)) fi;; esac done while (($j > 0)); do ((j--)) echo -n "/${rev_canon[$j]}" done echo } canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/" 

Ich habe den folgenden Ansatz für eine Weile erfolgreich verwendet (nicht auf OSX) und es verwendet nur die integrierte Shell und behandelt den Fall ‘source foobar.sh’ soweit ich gesehen habe.

Ein Problem mit dem unten stehenden Beispielcode besteht darin, dass die function $ PWD verwendet, die zum Zeitpunkt des functionsaufrufs korrekt sein kann oder nicht. Das muss also gehandhabt werden.

 #!/bin/bash function canonical_path() { # Handle realtive vs absolute path [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1 # Change to dirname of x cd ${x%/*} # Combine new pwd with basename of x echo $(pwd -P)/${x##*/} cd $OLDPWD } echo $(canonical_path "${BASH_SOURCE[0]}") type [ type cd type echo type pwd 

Noch eine andere Möglichkeit dies zu tun:

 shopt -s extglob selfpath=$0 selfdir=${selfpath%%+([!/])} while [[ -L "$selfpath" ]];do selfpath=$(readlink "$selfpath") if [[ ! "$selfpath" =~ ^/ ]];then selfpath=${selfdir}${selfpath} fi selfdir=${selfpath%%+([!/])} done echo $selfpath $selfdir 

Einzeiler

 `dirname $(realpath $0)` 

Einfacher gesagt funktioniert das für mich:

 MY_DIR=`dirname $0` source $MY_DIR/_inc_db.sh