Python: Ändern Sie das Arbeitsverzeichnis des Skripts in das eigene Verzeichnis des Skripts

171

Ich starte jede Minute eine Python-Shell von crontab:

* * * * * /home/udi/foo/bar.py

/home/udi/foohat einige notwendige Unterverzeichnisse, wie /home/udi/foo/logund /home/udi/foo/config, die sich /home/udi/foo/bar.pybeziehen.

Das Problem ist, dass crontabdas Skript in einem anderen Arbeitsverzeichnis ausgeführt wird, sodass der Versuch, es zu öffnen, ./log/bar.logfehlschlägt.

Gibt es eine gute Möglichkeit, das Skript anzuweisen, das Arbeitsverzeichnis in das eigene Verzeichnis des Skripts zu ändern? Ich würde mir eine Lösung vorstellen, die für jeden Skriptstandort funktioniert, anstatt dem Skript explizit mitzuteilen, wo es sich befindet.

BEARBEITEN:

os.chdir(os.path.dirname(sys.argv[0]))

War die kompakteste elegante Lösung. Vielen Dank für Ihre Antworten und Erklärungen!

Adam Matan
quelle
unabhängig vom crontabAnwendungsfall: beides sys.argv[0]und __file__fehlgeschlagen, wenn das Skript mit ausgeführt wird execfile(); Stattdessen könnte eine inspectLösung auf Basis verwendet werden.
JFS

Antworten:

206

Dadurch wird Ihr aktuelles Arbeitsverzeichnis in geändert, sodass das Öffnen relativer Pfade funktioniert:

import os
os.chdir("/home/udi/foo")

Sie haben jedoch gefragt, wie Sie in das Verzeichnis wechseln sollen, in dem sich Ihr Python-Skript befindet, auch wenn Sie nicht wissen, in welchem ​​Verzeichnis sich das Skript befindet. Dazu können Sie folgende os.pathFunktionen verwenden:

import os

abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)

Dies nimmt den Dateinamen Ihres Skripts, konvertiert ihn in einen absoluten Pfad, extrahiert dann das Verzeichnis dieses Pfads und wechselt dann in dieses Verzeichnis.

Eli Courtwright
quelle
3
Entspricht der Hardcodierung des Verzeichnisses.
Ikke
2
Wenn Sie es über einen Symlink ausführen, funktioniert dies nicht. Verwenden Sie __file__anstelle von sys.argv[0].
Chris Down
1
Warum der Abspath-Schritt? Warum nicht einfach os.chdir(os.path.dirname(__file__))?
Colonel Panic
8
__file__schlägt in "eingefrorenen" Programmen fehl (erstellt mit py2exe, PyInstaller, cx_Freeze). sys.argv[0]funktioniert. @ChrisDown: Wenn Sie Symlinks folgen möchten; os.path.realpath()könnte verwendet werden.
JFS
3
@EliCourtwright Wenn dies __file__noch kein absoluter Pfad ist und der Benutzer das Arbeitsverzeichnis geändert hat, schlägt dies os.path.abspathtrotzdem fehl.
Arthur Tacca
45

Sie können eine kürzere Version erhalten, indem Sie verwenden sys.path[0].

os.chdir(sys.path[0])

Von http://docs.python.org/library/sys.html#sys.path

Wie beim Programmstart initialisiert, ist das erste Element dieser Liste path[0]das Verzeichnis, das das Skript enthält, mit dem der Python-Interpreter aufgerufen wurde

xverges
quelle
23

Tu das nicht.

Ihre Skripte und Ihre Daten sollten nicht in einem großen Verzeichnis zusammengefasst werden. Setzen Sie den Code in irgendeiner bekannten Position ( site-packagesoder /var/opt/udioder etwas) getrennt von Ihren Daten. Verwenden Sie eine gute Versionskontrolle für Ihren Code, um sicherzustellen, dass aktuelle und frühere Versionen voneinander getrennt sind, damit Sie auf frühere Versionen zurückgreifen und zukünftige Versionen testen können.

Fazit: Mischen Sie Code und Daten nicht.

Daten sind wertvoll. Code kommt und geht.

Geben Sie das Arbeitsverzeichnis als Befehlszeilenargumentwert an. Sie können einen Standard als Umgebungsvariable angeben. Leiten Sie es nicht ab (oder raten Sie es)

Machen Sie es zu einem erforderlichen Argumentwert und tun Sie dies.

import sys
import os
working= os.environ.get("WORKING_DIRECTORY","/some/default")
if len(sys.argv) > 1: working = sys.argv[1]
os.chdir( working )

Nehmen Sie kein Verzeichnis an, das auf dem Speicherort Ihrer Software basiert. Es wird auf lange Sicht nicht gut funktionieren.

S.Lott
quelle
9
Ich denke, Sie haben Recht, wenn es darum geht, Code und Daten für große Softwarepakete zu trennen, aber für ein kleines Wartungsskript scheint dies ziemlich weit hergeholt zu sein. Ich stimme der Versionskontrolle voll und ganz zu.
Adam Matan
3
S. Lott hat recht. Halten Sie Daten und Code immer getrennt, es sei denn, die Daten sind nicht vorübergehend. Wenn Sie beispielsweise Symbole haben, sind dies Daten, die jedoch nicht vorübergehend sind, und es ist sinnvoll, sie in Bezug auf das Softwarepaket zu betrachten (was auch immer das bedeutet)
Stefano Borini,
5
@ Udi Pasmon: Überhaupt nicht weit hergeholt. Es sind die "kleinen Wartungsskripte", die Unternehmen in große Schwierigkeiten bringen. In Jahren wird dieses "kleine Wartungsskript" mit seinen untergeordneten Elementen, Ableitungen und Datendateien ein Albtraum sein, der entwirrt und neu implementiert werden muss. Halten Sie Daten so weit wie möglich vom Code entfernt - übergeben Sie Parameter für alles - nehmen Sie nichts an.
S.Lott
1
+1 Ich dachte, ich wollte als OP arbeiten, aber nachdem ich Ihren Rat gelesen hatte, änderte ich stattdessen mein Skript. Jetzt ist ein Parameter erforderlich, um den Speicherort einer Protokolldatei anzugeben.
Iain Samuel McLean Elder
+1. Es ist einfacher, ein Paket (rpm) für ein Python-Skript zu erstellen, wenn Datenverzeichnisse einfach angepasst werden können.
JFS
18

Ändern Sie Ihren crontab-Befehl in

* * * * * (cd /home/udi/foo/ || exit 1; ./bar.py)

Das (...)startet eine Sub-Shell, die Ihr Crond als einzelnen Befehl ausführt. Das || exit 1bewirkt , dass Ihr cronjob im Fall fehlschlagen , dass das Verzeichnis nicht verfügbar ist.

Obwohl die anderen Lösungen auf lange Sicht für Ihre spezifischen Skripte eleganter sind, kann mein Beispiel dennoch nützlich sein, wenn Sie das Programm oder den Befehl, den Sie ausführen möchten, nicht ändern können.

Ruud Althuizen
quelle
1
Dies ist eine äußerst solide Lösung. Normalerweise bearbeite ich die Antworten anderer Leute, um Dinge wie die hinzuzufügen || exit 1. Es ist erfrischend, das zu sehen. Obwohl ich mich fragen muss, warum Sie es nicht einfach tun würdencd /home/udi/foo/ && ./bar.py
Bruno Bronosky
2
@BrunoBronosky Mit dem expliziten wird exit 1Ihr crond über einen Fehler benachrichtigt und in den meisten Fällen eine E-Mail-Benachrichtigung über den Fehler gesendet .
Ruud Althuizen