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 :)

9voto

AsymLabs Punkte 863

Die beste kompakte Lösung wäre meiner Meinung nach:

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

Es gibt keine Abhängigkeit von etwas anderem als Bash. Die Verwendung von dirname , readlink y basename zu Kompatibilitätsproblemen führen, so dass sie am besten vermieden werden sollten, wenn dies möglich ist.

4 Stimmen

Sie sollten wahrscheinlich einen Schrägstrich hinzufügen: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )" . Sie würden sonst Probleme mit dem Root-Verzeichnis bekommen. Und warum müssen Sie überhaupt echo verwenden?

0 Stimmen

dirname y basename sind POSIX-standardisiert, warum sollte man sie also nicht verwenden? Links: dirname , basename

0 Stimmen

Die Vermeidung von zwei zusätzlichen Prozessgabeln und das Festhalten an Shell-Build-ins könnte ein Grund dafür sein.

9voto

Geoff Nixon Punkte 4159

Ich glaube, ich habe das hier. Ich bin zwar spät dran, aber ich denke, einige werden es zu schätzen wissen, dass es hier steht, wenn sie auf diesen Thread stoßen. Die Kommentare sollten es erklären:

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

8voto

mrucci Punkte 4192

Dies ist ärgerlicherweise der einzige Einzeiler, den ich gefunden habe, der sowohl unter Linux als auch unter macOS funktioniert, wenn das ausführbare Skript ein Symlink ist:

SCRIPT_DIR=$(python -c "import os; print(os.path.dirname(os.path.realpath('${BASH_SOURCE[0]}')))")

oder in ähnlicher Weise mit dem Python3-Modul pathlib:

SCRIPT_DIR=$(python3 -c "from pathlib import Path; print(Path('${BASH_SOURCE[0]}').resolve().parent)")

Getestet auf Linux und macOS und verglichen mit anderen Lösungen in diesem Gist: https://gist.github.com/ptc-mrucci/61772387878ed53a6c717d51a21d9371

1 Stimmen

Warum importieren Sie sys ?

0 Stimmen

Entfernt. Es funktioniert auch mit $0 anstelle von ${BASH_SOURCE[0] wenn Sie nicht erwarten, dass das Skript ausgelagert wird.

8voto

LozanoMatheus Punkte 323

Sie können das tun, indem Sie den Skriptnamen kombinieren ( $0 ) mit realpath und/oder dirname . Es funktioniert für Bash und Shell.

#!/usr/bin/env bash

RELATIVE_PATH="${0}"
RELATIVE_DIR_PATH="$(dirname "${0}")"
FULL_DIR_PATH="$(realpath "${0}" | xargs dirname)"
FULL_PATH="$(realpath "${0}")"

echo "RELATIVE_PATH->${RELATIVE_PATH}<-"
echo "RELATIVE_DIR_PATH->${RELATIVE_DIR_PATH}<-"
echo "FULL_DIR_PATH->${FULL_DIR_PATH}<-"
echo "FULL_PATH->${FULL_PATH}<-"

Die Ausgabe sieht dann etwa so aus:

# RELATIVE_PATH->./bin/startup.sh<-
# RELATIVE_DIR_PATH->./bin<-
# FULL_DIR_PATH->/opt/my_app/bin<-
# FULL_PATH->/opt/my_app/bin/startup.sh<-

$0 ist der Name des Skripts selbst

4.4. Besondere Variablentypen

Ein Beispiel: LozanoMatheus/get_script_paths.sh

7voto

Alexander Stohr Punkte 153

Die obere Antwort funktioniert nicht in allen Fällen...

Da ich Probleme mit der BASH_SOURCE mit dem mitgelieferten 'cd'-Ansatz auf einigen sehr frischen und auch auf weniger frisch installierten Ubuntu 16.04 (Xenial Xerus)-Systemen das Shell-Skript mit "sh my_script.sh" aufruft, habe ich etwas anderes ausprobiert, das für meine Zwecke bisher ganz gut zu funktionieren scheint. Der Ansatz ist etwas kompakter im Skript und fühlt sich zudem viel weniger kryptisch an.

Bei diesem alternativen Ansatz werden die externen Anwendungen ' realpath ' und ' Verzeichnisname ' aus dem Paket coreutils. (Okay, nicht jeder mag den Overhead des Aufrufs sekundärer Prozesse - aber wenn man die mehrzeilige Skripterstellung für die Auflösung des wahren Objekts sieht, ist es auch nicht so schlimm, wenn man es in einer einzigen Binärdatei löst).

Sehen wir uns also ein Beispiel für diese alternativen Lösungen für die beschriebene Aufgabe der Abfrage des wahren absoluten Pfads zu einer bestimmten Datei an:

PATH_TO_SCRIPT=`realpath -s $0`
PATH_TO_SCRIPT_DIR=`dirname $PATH_TO_SCRIPT`

Aber vorzugsweise sollten Sie diese weiterentwickelte Version verwenden, um auch die Verwendung von Pfaden mit Leerzeichen (oder vielleicht sogar einigen anderen Sonderzeichen) zu unterstützen:

PATH_TO_SCRIPT=`realpath -s "$0"`
PATH_TO_SCRIPT_DIR=`dirname "$PATH_TO_SCRIPT"`

Wenn Sie den Wert der SCRIPT-Variablen nicht benötigen, können Sie diesen Zweizeiler sogar in einer einzigen Zeile zusammenfassen. Aber warum sollten Sie sich dafür wirklich die Mühe machen?

0 Stimmen

Diese Frage ist bash spezifisch. Wenn Sie ein Skript aufrufen mit sh kann die Hülle etwas anderes sein, z. B. zsh o dash .

0 Stimmen

Ich werde den code jetzt nicht überprüfen - aber sie können ihn mit "bash" aufrufen, wenn sie wollen. sehen sie "sh" nur als alias für die binärbasierte auswahl des kompatiblen shell executors.

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