it-swarm.com.de

Zuverlässige Möglichkeit für ein Bash-Skript, den vollständigen Pfad zu sich selbst zu finden

Ich habe ein Bash-Skript, das seinen vollständigen Pfad kennen muss. Ich versuche, einen weitgehend kompatiblen Weg zu finden, um dies zu erreichen, ohne relative oder funky aussehende Pfade zu finden. Ich muss nur Bash unterstützen, nicht sh, csh usw.

Was ich bisher gefunden habe:

  1. Die akzeptierte Antwort auf Abrufen des Quellverzeichnisses eines Bash-Skripts von innen adressiert das Abrufen des Pfad des Skripts über dirname $0, was in Ordnung ist Dies kann jedoch einen relativen Pfad (wie .) zurückgeben. Dies ist ein Problem, wenn Sie die Verzeichnisse im Skript ändern möchten und der Pfad immer noch auf den verweist Skriptverzeichnis. Trotzdem wird dirname Teil des Puzzles sein.

  2. Die akzeptierte Antwort auf Bash-Skript absoluter Pfad mit OS X (OS X spezifisch, aber die Antwort funktioniert unabhängig davon) gibt eine Funktion an, mit der geprüft wird, ob $0 relativ aussieht, und wenn ja, wird $PWD vorangestellt. Aber das Ergebnis kann immer noch relative Bits enthalten (obwohl es insgesamt absolut ist) - zum Beispiel, wenn das Skript t im Verzeichnis /usr/bin ist und Sie sich in /usr befinden und tippen bin/../bin/t Um es auszuführen (ja, das ist kompliziert), müssen Sie /usr/bin/../bin als Verzeichnispfad des Skripts angeben. Welches 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 basiert die Lösung auf GNUs readlink, wo BSDs aus irgendeinem Grund nicht funktionieren (ich habe keinen Zugriff auf ein BSD-ähnliches System).

Also, verschiedene Arten, es zu tun, aber sie alle haben ihre Vorbehalte.

Was wäre ein besserer Weg? Wo "besser" bedeutet:

  • Gibt mir den absoluten Weg.
  • Entfernt flippige Bits, selbst wenn sie auf verschlungene Weise aufgerufen werden (siehe Kommentar zu # 2 oben). (Zum Beispiel kanonisiert der Pfad zumindest mäßig.)
  • Verlässt sich nur auf Bash-Ismen oder Dinge, die mit ziemlicher Sicherheit auf den beliebtesten Varianten von * nix-Systemen basieren (GNU/Linux, BSD und BSD-ähnliche Systeme wie OS X usw.).
  • Vermeidet nach Möglichkeit das Aufrufen externer Programme (z. B. bevorzugt integrierte Bash-Funktionen).
  • ( Aktualisiert , danke für die Hinweise, wich ) Symlinks müssen nicht aufgelöst werden. d bevorzugen es, sie in Ruhe zu lassen, aber das ist keine Voraussetzung).
622
T.J. Crowder

Folgendes habe ich mir ausgedacht (redigieren: plus einige Verbesserungen durch sfstewman , levigroker , Kyle Strand und Rob Kennedy ), das scheint hauptsächlich meinen "besseren" Kriterien zu entsprechen:

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

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

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

500
T.J. Crowder

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

Ihre anfängliche Lösung wird:

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

Und um symbolische Links nach Ihren Wünschen ungelöst zu lassen:

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

Die einfachste Möglichkeit, einen vollständigen kanonischen Pfad in Bash zu finden, besteht darin, cd und pwd zu verwenden:

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

Die Verwendung von ${BASH_SOURCE[0]} anstelle von $0 führt zu demselben Verhalten, unabhängig davon, ob das Skript als <name> oder source <name> aufgerufen wird.

159
Andrew Norrie

Ich musste dieses Problem heute noch einmal überprüfen und fand Holen Sie sich das Quellverzeichnis eines Bash-Skripts aus dem Skript selbst:

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

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

46
Felix Rabe

Ruft den absoluten Pfad eines Shell-Skripts ab

Die Option -f wird in readlink nicht verwendet und sollte daher unter BSD/Mac OS X funktionieren.

Unterstützt

  • source ./script (Beim Aufruf durch den Punktoperator .)
  • Absoluter Pfad/Pfad/zu/Skript
  • Relativer Pfad wie ./script
  • /path/dir1/../dir2/dir3/../script
  • Bei Aufruf von symlink
  • Wenn symlink verschachtelt ist, zB) foo->dir1/dir2/bar bar->./../doe doe->script
  • Wenn der Anrufer den Namen des Skripts ändert

Ich suche Eckfälle, 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`]"

Bekannter issus

Das Skript muss sich irgendwo auf der Festplatte befinden . Lass es über ein Netzwerk sein. Wenn Sie versuchen, dieses Skript über eine PIPE auszuführen, funktioniert es nicht

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

Technisch gesehen ist es undefiniert. Praktisch gibt es keinen vernünftigen Weg, dies zu erkennen. (Ein Co-Prozess kann nicht auf die Umgebung des übergeordneten Elements zugreifen.)

36
GreenFox

Verwenden:

SCRIPT_PATH=$(dirname `which $0`)

which gibt den vollständigen Pfad der ausführbaren Datei aus, der ausgeführt worden wäre, wenn das übergebene Argument an der Shell-Eingabeaufforderung eingegeben worden wäre (was $ 0 enthält).

dirname entfernt das Nicht-Verzeichnis-Suffix von einem Dateinamen.

Somit erhalten Sie den vollständigen Pfad des Skripts, unabhängig davon, ob der Pfad angegeben wurde oder nicht.

31
Matt

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

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

$SCRIPT enthält den tatsächlichen Dateipfad zum Skript und $SCRIPTPATH den tatsächlichen Pfad des Verzeichnisses, in dem sich das Skript befindet.

Bevor Sie dies benutzen, lesen Sie die Kommentare von diese Antwort .

24
ypid

Leicht zu lesen? Unten ist eine Alternative. Symlinks werden ignoriert

#!/bin/bash
currentDir=$(
  cd $(dirname "$0")
  pwd
)

echo -n "current "
pwd
echo script $currentDir

Seitdem ich die obige Antwort vor ein paar Jahren gepostet habe, habe ich meine Praxis dahingehend weiterentwickelt, dass ich dieses linuxspezifische Paradigma verwende, das Symlinks richtig behandelt:

Origin=$(dirname $(readlink -f $0))
13
gerardw

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
10
Stormcloud

Wir haben unser eigenes Produkt realpath-lib auf GitHub kostenlos und uneingeschränkt für die Community bereitgestellt.

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

get_realpath <absolute|relative|symlink|local file>

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 sind keine externen Abhängigkeiten erforderlich, nur Bash 4+. Enthält auch Funktionen für get_dirname, get_filename, get_stemname und validate_path validate_realpath. Es ist kostenlos, übersichtlich, einfach und gut dokumentiert, sodass es auch zu Lernzwecken 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 Funktion durch etwas ersetzt, das das gleiche Ergebnis erzielt (ohne Verwendung von 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

}

Dazu gehört auch die Umgebungseinstellung no_symlinks, mit der Symlinks zum physischen System aufgelöst werden können. Standardmäßig bleiben Symlinks intakt.

7
AsymLabs

Einfach:

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

Ausgefallene Operatoren werden nicht benötigt.

7
poussma

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

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

Oder Sie können die folgende Funktion in Bash ausprobieren:

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

Diese Funktion benötigt ein Argument. Wenn das Argument bereits einen absoluten Pfad hat, drucken Sie ihn so, wie er ist, andernfalls drucken Sie $PWD Variable + Dateinamenargument (ohne ./ Präfix).

Verbunden:

5
kenorb

Bourne Shell (sh) konform:

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

Betrachten wir dieses Problem noch einmal: Es gibt eine sehr beliebte Lösung, auf die in diesem Thread verwiesen wird und die ihren Ursprung hat hier :

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

Ich habe mich wegen der Verwendung von dirname von dieser Lösung ferngehalten - sie kann plattformübergreifende Schwierigkeiten bereiten, 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?

3
AsymLabs

Wenn wir Bash verwenden, ist dies meiner Meinung nach der bequemste Weg, da keine externen Befehle aufgerufen werden müssen:

THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
3
Nordlöw

Die akzeptierte Lösung hat (für mich) den Nachteil, nicht "quellfähig" zu sein:
Wenn Sie es von einem "source ../../yourScript" nennen, wäre $0 "bash"!

Die folgende Funktion (für bash> = 3.0) gibt mir den richtigen Pfad an, jedoch könnte das Skript aufgerufen werden (direkt oder durch source, mit einem absoluten oder einem relativen Pfad):
(mit "rechter Pfad" meine ich den vollständiger absoluter Pfad des aufgerufenen Skripts, auch wenn er von einem anderen Pfad direkt oder mit "source" aufgerufen wird)

#!/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 ist, den Fall "source" zu erkennen und ${BASH_SOURCE[0]} zu verwenden, um das eigentliche Skript zurückzugewinnen.

2
VonC

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

Wie kann ich das Verhalten von readlink -f von GNU auf einem Mac ermitteln?

Da Sie nur den Namen kanonisieren möchten, den Sie durch die Verkettung von $ PWD und $ 0 erhalten (vorausgesetzt, $ 0 ist anfangs nicht absolut), verwenden Sie einfach eine Reihe von Regex-Ersetzungen entlang der Linie von abs_dir=${abs_dir//\/.\//\/} und so weiter.

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

2
wich

Versuche dies:

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

Nur zum Teufel habe ich ein bisschen an einem Skript gehackt, das Dinge rein textuell und rein in Bash ausführt. Ich hoffe, ich habe alle Edge-Fälle abgefangen.

Beachten Sie, dass der ${var//pat/repl}, den ich in der anderen Antwort erwähnt habe, nicht funktioniert, da Sie ihn nicht dazu bringen können, nur die kürzestmögliche Übereinstimmung zu ersetzen. Dies ist ein Problem beim Ersetzen von /foo/../, wie z. /*/../ nimmt alles vor sich, nicht nur einen einzigen Eintrag. Und da diese Muster eigentlich keine regulären Ausdrücke sind, sehe ich nicht, wie das funktionieren kann. Also hier ist die schön verschlungene Lösung, die ich mir ausgedacht habe, viel Spaß. ;)

Übrigens, lassen Sie mich wissen, ob Sie unbehandelte Edge-Fä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/"
0
wich

Einzeiler

`dirname $(realpath $0)`
0
Abhijit

Ich habe den folgenden Ansatz eine Weile erfolgreich verwendet (allerdings nicht unter OS X), und er verwendet nur eine integrierte Shell und behandelt den Fall 'source foobar.sh', soweit ich das gesehen habe.

Ein Problem mit dem (hastig zusammengestellten) Beispielcode unten ist, dass die Funktion $ PWD verwendet, das zum Zeitpunkt des Funktionsaufrufs möglicherweise korrekt ist oder nicht. Das muss also gehandhabt werden.

#!/bin/bash

function canonical_path() {
  # Handle relative 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
0
andro