6176 Stimmen

Wie kann ich das Quellverzeichnis eines Bash-Skripts aus dem Skript selbst abrufen?

Wie erhalte ich den Pfad des Verzeichnisses, in dem ein Bash Skript befindet sich, innerhalb dieses Drehbuch?

Ich möchte ein Bash-Skript als Launcher für eine andere Anwendung verwenden. Ich möchte das Arbeitsverzeichnis in das Verzeichnis ändern, in dem sich das Bash-Skript befindet, damit ich mit den Dateien in diesem Verzeichnis arbeiten kann:

$ ./application

91 Stimmen

Keine der derzeitigen Lösungen funktioniert, wenn es irgendwelche Zeilenumbrüche am Ende des Verzeichnisnamens - Sie werden von der Befehlssubstitution entfernt. Um dies zu umgehen, können Sie ein Nicht-Neuzeilen-Zeichen innerhalb der Befehlsersetzung anhängen - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)" - und entfernen Sie es ohne Befehlssubstitution - DIR="${DIR%x}" .

94 Stimmen

@jpmc26 Es gibt zwei sehr häufige Situationen: Unfälle und Sabotage. Ein Skript sollte nicht auf unvorhersehbare Weise versagen, nur weil jemand, irgendwo, eine mkdir $'\n' .

36 Stimmen

Wer Leute sein System auf diese Weise sabotieren lässt, sollte es nicht der Bash überlassen, solche Probleme zu erkennen... und schon gar nicht Leute einstellen, die zu solchen Fehlern fähig sind. Ich habe in den 25 Jahren, in denen ich die Bash benutze, noch nie erlebt, dass so etwas irgendwo passiert.... Deshalb gibt es Dinge wie Perl und Praktiken wie Taint Checking (ich werde wahrscheinlich dafür geflamed werden, dass ich das sage :)

8011voto

dogbane Punkte 253146
#!/usr/bin/env bash

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

ist ein nützlicher Einzeiler, der Ihnen den vollständigen Verzeichnisnamen des Skripts anzeigt, egal von wo aus es aufgerufen wird.

Es funktioniert, solange die letzte Komponente des Pfades, der zum Auffinden des Skripts verwendet wird, kein Symlink ist (Verzeichnislinks sind OK). Wenn Sie auch Links zum Skript selbst auflösen wollen, benötigen Sie eine mehrzeilige Lösung:

#!/usr/bin/env bash

SOURCE=${BASH_SOURCE[0]}
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
  SOURCE=$(readlink "$SOURCE")
  [[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )

Letzteres funktioniert mit jeder Kombination von Aliasen, source , bash -c Symlinks, usw.

Vorsicht! wenn Sie cd in ein anderes Verzeichnis wechseln, bevor Sie dieses Snippet ausführen, kann das Ergebnis falsch sein!

Achten Sie auch auf $CDPATH gotchas und Seiteneffekte der stderr-Ausgabe, wenn der Benutzer cd geschickt überschrieben hat, um die Ausgabe stattdessen nach stderr umzuleiten (einschließlich Escape-Sequenzen, wie beim Aufruf von update_terminal_cwd >&2 auf Mac). Hinzufügen von >/dev/null 2>&1 am Ende Ihrer cd Befehl werden beide Möglichkeiten berücksichtigt.

Um zu verstehen, wie es funktioniert, versuchen Sie, diese ausführlichere Form auszuführen:

#!/usr/bin/env bash

SOURCE=${BASH_SOURCE[0]}
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  TARGET=$(readlink "$SOURCE")
  if [[ $TARGET == /* ]]; then
    echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
    SOURCE=$TARGET
  else
    DIR=$( dirname "$SOURCE" )
    echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
    SOURCE=$DIR/$TARGET # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR=$( dirname "$SOURCE" )
DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

Und es wird etwas gedruckt wie:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

38 Stimmen

Sie können diesen Ansatz mit der Antwort von Benutzer25866 kombinieren, um eine Lösung zu finden, die mit source <script> y bash <script> : DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" .

25 Stimmen

Manchmal cd gibt etwas auf STDOUT aus! Zum Beispiel, wenn Ihr $CDPATH hat . . Um diesen Fall abzudecken, verwenden Sie DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"

248 Stimmen

Diese akzeptierte Antwort ist nicht in Ordnung, sie funktioniert nicht mit Symlinks und ist übermäßig komplex. dirname $(readlink -f $0) ist der richtige Befehl. Siehe gist.github.com/tvlooy/cbfbdb111a4ebad8b93e für einen Testfall

1213voto

matt b Punkte 135206

Verwenden Sie dirname "$0" :

#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"

Verwendung von pwd allein wird nicht funktionieren, wenn Sie das Skript nicht aus dem Verzeichnis ausführen, in dem es enthalten ist.

[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp

33 Stimmen

Für die Portabilität über die Bash hinaus ist $0 vielleicht nicht immer ausreichend. Sie müssen möglicherweise "type -p $0" ersetzen, damit dies funktioniert, wenn der Befehl im Pfad gefunden wurde.

14 Stimmen

@Darron: Sie können nur verwenden type -p wenn das Skript ausführbar ist. Dies kann auch ein subtiles Loch öffnen, wenn das Skript mit bash test2.sh und es gibt ein weiteres Skript mit demselben Namen, das irgendwo anders ausgeführt wird.

122 Stimmen

@Darron: aber da die Frage mit einem Tag versehen ist bash und in der Hash-Bang-Zeile wird ausdrücklich erwähnt /bin/bash Ich würde sagen, es ist ziemlich sicher, sich auf Bashismen zu verlassen.

670voto

phatblat Punkte 3377

Le site dirname ist der einfachste Befehl, der einfach den Pfad bis zum Dateinamen aus der $0 (Name des Skripts) variieren:

dirname "$0"

Aber, wie matt b darauf hingewiesen, ist der zurückgegebene Pfad unterschiedlich, je nachdem, wie das Skript aufgerufen wird. pwd erfüllt die Aufgabe nicht, da es nur das aktuelle Verzeichnis angibt, nicht aber das Verzeichnis, in dem sich das Skript befindet. Wenn ein symbolischer Link zu einem Skript ausgeführt wird, erhalten Sie außerdem einen (wahrscheinlich relativen) Pfad zu dem Verzeichnis, in dem sich der Link befindet, und nicht das eigentliche Skript.

Einige andere haben erwähnt, dass die readlink Befehl, aber am einfachsten ist es, ihn zu verwenden:

dirname "$(readlink -f "$0")"

readlink löst den Skriptpfad in einen absoluten Pfad vom Stamm des Dateisystems auf. Daher werden alle Pfade, die einfache oder doppelte Punkte, Tilden und/oder symbolische Links enthalten, in einen vollständigen Pfad aufgelöst.

Hier ist ein Skript, das jeden dieser Punkte demonstriert, whatdir.sh :

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"

Ich führe dieses Skript in meinem Heimatverzeichnis aus und verwende einen relativen Pfad:

>>>$ ./whatdir.sh
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

Wiederum unter Verwendung des vollständigen Pfads zum Skript:

>>>$ /Users/phatblat/whatdir.sh
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Jetzt wechseln Sie die Verzeichnisse:

>>>$ cd /tmp
>>>$ ~/whatdir.sh
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Und schließlich die Verwendung eines symbolischen Links zur Ausführung des Skripts:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

20 Stimmen

readlink ist bei einigen Plattformen in der Standardinstallation nicht verfügbar. Versuchen Sie, die Verwendung zu vermeiden, wenn Sie können

55 Stimmen

Achten Sie darauf, alles in Anführungszeichen zu setzen, um Probleme mit Leerzeichen zu vermeiden: export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

15 Stimmen

In OSX Yosemite 10.10.1 -f wird nicht als Option für readlink . Verwendung von stat -f erledigt stattdessen die Arbeit. Danke

205voto

user25866 Punkte 301
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd "$(dirname "$SCRIPT_PATH")";
  SCRIPT_PATH=$(readlink "${SCRIPT_PATH}"); done
fi
cd "$(dirname ${SCRIPT_PATH})" > /dev/null
SCRIPT_PATH=$(pwd);
popd  > /dev/null

Es funktioniert für alle Versionen, einschließlich

  • wenn sie über einen Softlink mit mehreren Tiefen aufgerufen werden,
  • wenn die Datei es
  • wenn das Skript mit dem Befehl " source " aka . (Punkt)-Operator.
  • wenn arg $0 wird vom Anrufer geändert.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Alternativ, wenn das Bash-Skript selbst ein relativer symbolischer Link Sie wollen um ihm zu folgen und den vollständigen Pfad des verknüpften Skripts zurückzugeben:

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

SCRIPT_PATH wird in vollem Pfad angegeben, egal wie er aufgerufen wird.

Stellen Sie nur sicher, dass Sie dies am Anfang des Skripts finden.

4 Stimmen

Schön! Könnte kürzer gemacht werden, indem man "pushd[...] popd /dev/null" durch SCRIPT_PATH= ersetzt readlink -f $(dirname "${VIRTUAL_ENV}") ;

1 Stimmen

Dies ist bei weitem die "stabilste" Version, die ich gesehen habe. Vielen Dank dafür!

1 Stimmen

Sollte der Doppelpunkt in Zeile 1 nicht in Zeile zwei zwischen die if- und then-Anweisungen verschoben werden?

145voto

Thamme Gowda Punkte 10173

Hier ist ein leicht zu merkendes Skript:

DIR=$(dirname "${BASH_SOURCE[0]}")   # Get the directory name
DIR=$(realpath "${DIR}")    # Resolve its full path if need be

14 Stimmen

Oder, noch unklarer, auf einer Linie: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")

11 Stimmen

Warum ist dies nicht die akzeptierte Antwort? Gibt es einen Unterschied bei der Verwendung von realpath vom "manuellen" Lösen mit einer Schleife von readlink ? Auch die readlink Manpage sagt Note realpath(1) is the preferred command to use for canonicalization functionality.

5 Stimmen

Und übrigens: Sollten wir nicht auch die realpath vor dirname und nicht danach? Wenn die Skriptdatei selbst ein Symlink ist... Es würde etwas ergeben wie DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")" . Das kommt der von Simon vorgeschlagenen Antwort schon sehr nahe.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X