Unix-Shell-Skript Finden Sie heraus, in welchem ​​Verzeichnis sich die Skriptdatei befindet.

511

Grundsätzlich muss ich das Skript mit Pfaden ausführen, die sich auf den Speicherort der Shell-Skriptdatei beziehen. Wie kann ich das aktuelle Verzeichnis in dasselbe Verzeichnis ändern, in dem sich die Skriptdatei befindet?

William Yeung
quelle
12
Ist das wirklich ein Duplikat? Diese Frage bezieht sich auf ein "Unix-Shell-Skript", die andere speziell auf Bash.
Michaeljt
2
@ BoltClock: Diese Frage wurde nicht ordnungsgemäß geschlossen. Die verknüpfte Frage bezieht sich auf Bash. Diese Frage betrifft die Unix-Shell-Programmierung. Beachten Sie, dass die akzeptierten Antworten sehr unterschiedlich sind!
Dietrich Epp
@Dietrich Epp: Du hast recht. Es scheint, dass die Wahl der akzeptierten Antwort durch den Fragesteller und das Hinzufügen des [bash] -Tags (wahrscheinlich als Antwort darauf) mich dazu veranlasst haben, die Frage als Duplikat als Antwort auf eine Flagge zu markieren.
BoltClock
2
Ich denke, diese Antwort ist besser: Das Quellverzeichnis eines Bash-Skripts von innen heraus
abrufen

Antworten:

568

In Bash sollten Sie das bekommen, was Sie brauchen:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"
TheMarko
quelle
17
Dies funktioniert nicht, wenn Sie das Skript über einen symbolischen Link in einem anderen Verzeichnis aufgerufen haben. Damit dies funktioniert, müssen Sie readlinkauch verwenden (siehe Antwort von al unten)
AndrewR
44
In Bash ist es sicherer, $BASH_SOURCEanstelle von zu verwenden $0, da $0es nicht immer den Pfad des aufgerufenen Skripts enthält, z. B. beim "Sourcing" eines Skripts.
mklement0
2
$BASH_SOURCEist Bash-spezifisch, die Frage betrifft das Shell-Skript im Allgemeinen.
Ha-Duong Nguyen
7
@auraham: CUR_PATH=$(pwd)oder pwdgeben Sie das aktuelle Verzeichnis zurück (das nicht das übergeordnete Verzeichnis des Skripts sein muss)!
Andreas Dietrich
5
Ich habe die empfohlene Methode @ mklement0 ausprobiert $BASH_SOURCEund sie gibt das zurück, was ich brauchte. Mein Skript wird von einem anderen Skript aufgerufen und gibt $0zurück, .während $BASH_SOURCEdas richtige Unterverzeichnis (in meinem Fall scripts) zurückgegeben wird.
David Rissato Cruz
401

Der ursprüngliche Beitrag enthält die Lösung (ignorieren Sie die Antworten, sie fügen nichts Nützliches hinzu). Die interessante Arbeit erledigt der erwähnte Unix-Befehl readlinkmit Option -f. Funktioniert, wenn das Skript sowohl von einem absoluten als auch von einem relativen Pfad aufgerufen wird.

Für bash, sh, ksh:

#!/bin/bash 
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH

Für tcsh gilt csh:

#!/bin/tcsh
# Absolute path to this script, e.g. /home/user/bin/foo.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH

Siehe auch: https://stackoverflow.com/a/246128/59087

al.
quelle
12
Hinweis: Nicht alle Systeme haben readlink. Aus diesem Grund habe ich die Verwendung von pushd / popd (integrierte Funktionen für Bash) empfohlen.
docwhat
23
Die -fOption, readlinkunter OS X (Lion) und möglicherweise BSD etwas anderes zu tun. stackoverflow.com/questions/1055671/…
Ergwun
9
Zur Verdeutlichung des Kommentars von @ Ergwun: Wird unter -fOS X (ab Lion) überhaupt nicht unterstützt. Dort können Sie entweder das ablegen -f, um mit dem Auflösen höchstens einer Indirektionsebene auszukommen, z. B. pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"oder Sie können Ihr eigenes rekursives Symlink-Follow-Skript erstellen, wie im verlinkten Beitrag gezeigt.
mklement0
2
Ich verstehe immer noch nicht, warum das OP den absoluten Weg brauchen würde. Berichterstattung "." sollte in Ordnung funktionieren, wenn Sie auf Dateien relativ zum Skriptpfad zugreifen möchten und das Skript wie ./myscript.sh
Stefan Haberl
10
@StefanHaberl Ich denke, es wäre ein Problem, wenn Sie das Skript ausführen würden, während sich Ihr aktuelles Arbeitsverzeichnis vom Speicherort des Skripts unterscheidet (z. B. wäre sh /some/other/directory/script.sh)in diesem Fall .Ihr pwd, nicht/some/other/directory
Jon z
51

Ein früherer Kommentar zu einer Antwort sagte es, aber es ist leicht, unter allen anderen Antworten zu übersehen.

Bei Verwendung von Bash:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Bash-Referenzhandbuch, 5.2 Bash-Variablen

Daniel
quelle
3
Nur dieses funktioniert mit einem Skript im Umgebungspfad, die am häufigsten bewerteten funktionieren nicht. Vielen Dank!
Juli
Sie sollten dirname "$BASH_SOURCE"stattdessen Leerzeichen in $ BASH_SOURCE verwenden.
Mygod
1
Eine explizitere Methode zum Drucken des Verzeichnisses wäre laut Tool ShellCheck: "$ (dirname" $ ​​{BASH_SOURCE {0}} ")", da BASH_SOURCE ein Array ist und ohne den Index standardmäßig das erste Element verwendet wird .
Adrian M.
1
@AdrianM. , Sie wollen Klammern, nicht Klammern, für den Index:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown
Nicht gut für mich ... druckt nur einen Punkt (dh vermutlich das aktuelle Verzeichnis)
JL_SO
40

Angenommen, Sie verwenden Bash

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname $0)

echo $current_dir
echo $script_dir

Dieses Skript sollte das Verzeichnis drucken, in dem Sie sich befinden, und dann das Verzeichnis, in dem sich das Skript befindet. Wenn Sie es beispielsweise /mit dem Skript aufrufen /home/mez/, wird es ausgegeben

/
/home/mez

Denken Sie daran, wenn Sie Variablen aus der Ausgabe eines Befehls zuweisen, schließen Sie den Befehl ein $(und )- oder Sie erhalten nicht die gewünschte Ausgabe.

Mez
quelle
3
Dies funktioniert nicht, wenn ich das Skript aus dem aktuellen Verzeichnis aufrufe.
Eric Wang
1
@EricWang du bist immer im aktuellen Verzeichnis.
Strg-Alt-Delor
Für mich ist $ current_dir tatsächlich der Pfad, von dem aus ich das Skript aufrufe. $ Script_dir ist jedoch nicht das Verzeichnis des Skripts, sondern nur ein Punkt.
Michael
31

Wenn Sie Bash verwenden ...

#!/bin/bash

pushd $(dirname "${0}") > /dev/null
basedir=$(pwd -L)
# Use "pwd -P" for the path without links. man bash for more info.
popd > /dev/null

echo "${basedir}"
docwhat
quelle
4
Sie können das pushd/ popddurch cd $(dirname "${0}")und ersetzen cd -, damit es auf anderen Shells funktioniert, wenn diese eine haben pwd -L.
docwhat
warum würdest du hier pushd und popd verwenden?
Qodeninja
1
Ich muss das ursprüngliche Verzeichnis also nicht in einer Variablen speichern. Es ist ein Muster, das ich häufig in Funktionen und dergleichen verwende. Es nistet wirklich gut, was gut ist.
docwhat
Es wird immer noch im Speicher gespeichert - in einer Variablen - ob in Ihrem Skript auf eine Variable verwiesen wird oder nicht. Ich glaube auch, dass die Kosten für die Ausführung von pushd und popd die Einsparungen bei weitem nicht überwiegen, wenn in Ihrem Skript keine lokale Bash-Variable erstellt wird, sowohl in Bezug auf die CPU-Zyklen als auch in Bezug auf die Lesbarkeit.
Ingyhere
20

Wie der Marko vorschlägt:

BASEDIR=$(dirname $0)
echo $BASEDIR

Dies funktioniert nur, wenn Sie das Skript in demselben Verzeichnis ausführen, in dem sich das Skript befindet. In diesem Fall erhalten Sie den Wert '.'

Um dieses Problem zu umgehen, verwenden Sie:

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

Sie können jetzt die Variable current_dir in Ihrem Skript verwenden, um auf das Skriptverzeichnis zu verweisen. Dies kann jedoch immer noch das Symlink-Problem haben.

Ranamalo
quelle
20

Die beste Antwort auf diese Frage wurde hier beantwortet:
Abrufen des Quellverzeichnisses eines Bash-Skripts von innen

Und es ist:

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

Einzeiler, der Ihnen den vollständigen Verzeichnisnamen des Skripts gibt, unabhängig davon, von wo es aufgerufen wird.

Um zu verstehen, wie es funktioniert, können Sie das folgende Skript ausführen:

#!/bin/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" )" && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"
Alexandro de Oliveira
quelle
14
cd $(dirname $(readlink -f $0))
blau gefärbt
quelle
10

Machen wir es zu einem POSIX-Oneliner:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)

Getestet auf vielen Bourne-kompatiblen Schalen, einschließlich der BSD-Schalen.

Soweit ich weiß, bin ich der Autor und habe es gemeinfrei gemacht. Weitere Informationen finden Sie unter: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/

Ján Sáreník
quelle
1
wie geschrieben, cd: too many argumentswenn Leerzeichen im Pfad, und kehrt zurück $PWD. (eine offensichtliche Lösung, zeigt aber nur, wie viele
Randfälle
1
Würde positiv stimmen. Abgesehen von dem Kommentar von @michael, dass es mit Leerzeichen im Pfad fehlschlägt ... Gibt es eine Lösung dafür?
Spechter
1
@spechter ja, dafür gibt es eine Lösung. Siehe aktualisierte jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
Ján Sáreník
@Der_Meister - bitte genauer sein. Oder schreiben (und möglicherweise verschlüsseln) Sie eine E-Mail an jasan unter jasan.tk
Ján Sáreník
2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2 (sollte stattdessen den Pfad zum ursprünglichen Skript
anzeigen
10
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"
Suchrome
quelle
3
der zuverlässigste nicht-bash-spezifische Weg, den ich kenne.
Eddygeek
9

Wenn Sie das eigentliche Skriptverzeichnis abrufen möchten (unabhängig davon, ob Sie das Skript über einen Symlink oder direkt aufrufen), versuchen Sie Folgendes:

BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"

Dies funktioniert sowohl unter Linux als auch unter MacOS. Ich konnte hier niemanden erwähnen sehen realpath. Ich bin mir nicht sicher, ob dieser Ansatz Nachteile aufweist.

Unter MacOS müssen Sie installieren, coreutilsum verwenden zu können realpath. ZB : brew install coreutils.

Rohith
quelle
6

EINFÜHRUNG

Diese Antwort korrigiert die sehr kaputte, aber schockierend am besten gewählte Antwort dieses Threads (geschrieben von TheMarko):

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

WARUM FUNKTIONIERT DIE VERWENDUNG DES Dirnamens "$ 0" NICHT?

dirname $ 0 funktioniert nur, wenn der Benutzer das Skript auf eine ganz bestimmte Weise startet. Ich konnte mehrere Situationen finden, in denen diese Antwort fehlschlägt und das Skript zum Absturz bringt.

Lassen Sie uns zunächst verstehen, wie diese Antwort funktioniert. Er bekommt das Skriptverzeichnis, indem er es tut

dirname "$0"

$ 0 stellt den ersten Teil des Befehls dar, der das Skript aufruft (es ist im Grunde der eingegebene Befehl ohne die Argumente:

/some/path/./script argument1 argument2

$ 0 = "/ some / path /./ script"

dirname findet im Grunde das letzte / in einem String und schneidet es dort ab. Wenn Sie dies tun:

  dirname /usr/bin/sha256sum

Sie erhalten: / usr / bin

Dieses Beispiel funktioniert gut, da / usr / bin / sha256sum aber ein richtig formatierter Pfad ist

  dirname "/some/path/./script"

würde nicht gut funktionieren und würde dir geben:

  BASENAME="/some/path/." #which would crash your script if you try to use it as a path

Angenommen, Sie befinden sich im selben Verzeichnis wie Ihr Skript und starten es mit diesem Befehl

./script   

$ 0 in dieser Situation ist ./script und dirname $ 0 gibt an:

. #or BASEDIR=".", again this will crash your script

Verwenden von:

sh script

Ohne Eingabe des vollständigen Pfads wird auch ein BASEDIR = "."

Relative Verzeichnisse verwenden:

 ../some/path/./script

Gibt einen Dirnamen $ 0 von:

 ../some/path/.

Wenn Sie sich im Verzeichnis / some befinden und das Skript auf diese Weise aufrufen (beachten Sie das Fehlen von / am Anfang, wieder ein relativer Pfad):

 path/./script.sh

Sie erhalten diesen Wert für dirname $ 0:

 path/. 

und ./path/./script (eine andere Form des relativen Pfades) ergibt:

 ./path/.

Die einzigen zwei Situationen, in denen basedir $ 0 funktioniert, sind, wenn der Benutzer sh oder touch verwendet, um ein Skript zu starten, da beide zu $ ​​0 führen:

 $0=/some/path/script

Dadurch erhalten Sie einen Pfad, den Sie mit dirname verwenden können.

DIE LÖSUNG

Sie müssen jede der oben genannten Situationen berücksichtigen und erkennen und eine Korrektur dafür anwenden, wenn sie auftritt:

#!/bin/bash
#this script will only work in bash, make sure it's installed on your system.

#set to false to not see all the echos
debug=true

if [ "$debug" = true ]; then echo "\$0=$0";fi


#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)
                                                                     #situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)
                                                                     #situation3: different dir but relative path used to launch script
if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi                                 

if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1

_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
if [ "$B_" != "/" ]; then  #fix for situation3 #<- bash only
        if [ "$B_2" = "./" ]; then
                #covers ./relative_path/(./)script
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
        else
                #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
        fi
fi

if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi
thebunnyrules
quelle
4

Dieser Einzeiler gibt an, wo sich das Shell-Skript befindet. Dabei spielt es keine Rolle, ob Sie es ausgeführt haben oder ob Sie es bezogen haben . Außerdem werden alle beteiligten symbolischen Verknüpfungen aufgelöst, wenn dies der Fall ist:

dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))

Ich nehme an, Sie verwenden / bin / bash .

Richard Gomes
quelle
2

So viele Antworten, alle plausibel, jede mit Vor- und Nachteilen und leicht unterschiedlichen Zielen (die wahrscheinlich für jede angegeben werden sollten). Hier ist eine weitere Lösung, die das Hauptziel erfüllt, klar zu sein und systemübergreifend auf allen Bashs zu arbeiten (keine Annahmen über Bash-Versionen oderreadlink oderpwd -Optionen) und vernünftigerweise das zu tun, was Sie erwarten würden (z. B. das Auflösen von Symlinks ist eine interessantes Problem, aber normalerweise nicht das, was Sie tatsächlich wollen), behandeln Sie Randfälle wie Leerzeichen in Pfaden usw., ignorieren Sie alle Fehler und verwenden Sie einen vernünftigen Standard, wenn es Probleme gibt.

Jede Komponente wird in einer separaten Variablen gespeichert, die Sie einzeln verwenden können:

# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}      # this script's name
PROG_NAME=${PROG_PATH##*/}       # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
Michael
quelle
-4

Das sollte den Trick machen:

echo `pwd`/`dirname $0`

Es könnte hässlich aussehen, abhängig davon, wie es aufgerufen wurde und wie es mit dem CWD funktioniert, aber es sollte Sie dahin bringen, wo Sie hin müssen (oder Sie können die Zeichenfolge optimieren, wenn Sie sich dafür interessieren, wie sie aussieht).

Kenorb
quelle
1
Stackoverflow Escape Problem hier: Es sollte sicherlich so aussehen: `pwd`/`dirname $0`aber kann immer noch auf Symlinks fehlschlagen
Andreas Dietrich