python: remplacez le répertoire de travail des scripts par le propre répertoire du script

171

Je lance un shell python depuis crontab toutes les minutes:

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

/home/udi/fooa quelques sous-répertoires nécessaires, comme /home/udi/foo/loget /home/udi/foo/config, qui /home/udi/foo/bar.pyfait référence à.

Le problème est que crontable script exécute à partir d'un répertoire de travail différent, donc essayer d'ouvrir ./log/bar.logéchoue.

Existe-t-il une bonne façon de dire au script de changer le répertoire de travail vers le propre répertoire du script? J'aurais envie d'une solution qui fonctionnerait pour n'importe quel emplacement de script, plutôt que d'indiquer explicitement au script où il se trouve.

ÉDITER:

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

C'était la solution élégante la plus compacte. Merci pour vos réponses et explications!

Adam Matan
la source
sans rapport avec le crontabcas d' utilisation: les deux sys.argv[0]et __file__échouent si le script est exécuté en utilisant execfile(); inspectune solution à base de données pourrait être utilisée à la place.
jfs

Réponses:

206

Cela changera votre répertoire de travail actuel en afin que l'ouverture des chemins relatifs fonctionne:

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

Cependant, vous avez demandé comment changer dans le répertoire de votre script Python, même si vous ne savez pas quel répertoire ce sera lorsque vous écrivez votre script. Pour ce faire, vous pouvez utiliser les os.pathfonctions:

import os

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

Cela prend le nom de fichier de votre script, le convertit en chemin absolu, puis extrait le répertoire de ce chemin, puis se change dans ce répertoire.

Eli Courtwright
la source
3
Égale le codage en dur du répertoire.
Ikke
2
Si vous l'exécutez à partir d'un lien symbolique, cela ne fonctionnera pas. Utilisez à la __file__place de sys.argv[0].
Chris Down
1
Pourquoi l'étape abspath? Pourquoi pas simplement os.chdir(os.path.dirname(__file__))?
Colonel Panic
8
__file__échoue dans les programmes "gelés" (créés à l'aide de py2exe, PyInstaller, cx_Freeze). sys.argv[0]travaux. @ChrisDown: Si vous souhaitez suivre les liens symboliques; os.path.realpath()peut être utilisé.
jfs
3
@EliCourtwright Si ce __file__n'est pas déjà un chemin absolu et que l'utilisateur a changé le répertoire de travail, alors os.path.abspathéchouera de toute façon.
Arthur Tacca
45

Vous pouvez obtenir une version plus courte en utilisant sys.path[0].

os.chdir(sys.path[0])

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

Comme initialisé au démarrage du programme, le premier élément de cette liste, path[0]est le répertoire contenant le script qui a été utilisé pour appeler l'interpréteur Python

xverges
la source
23

Ne fais pas ça.

Vos scripts et vos données ne doivent pas être écrasés dans un seul grand répertoire. Mettez votre code dans un emplacement connu ( site-packagesou /var/opt/udiou quelque chose) séparé de vos données. Utilisez un bon contrôle de version sur votre code pour vous assurer que les versions actuelle et précédente sont séparées les unes des autres afin de pouvoir revenir aux versions précédentes et tester les versions futures.

Conclusion: ne mélangez pas le code et les données.

Les données sont précieuses. Le code va et vient.

Fournissez le répertoire de travail comme valeur d'argument de ligne de commande. Vous pouvez fournir une valeur par défaut comme variable d'environnement. Ne le déduisez pas (ou ne le devinez pas)

Faites-en une valeur d'argument obligatoire et faites-le.

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

N'assumez pas un répertoire basé sur l'emplacement de votre logiciel. Cela ne fonctionnera pas bien à long terme.

S.Lott
la source
9
Je pense que vous avez raison de séparer le code et les données pour les grands progiciels, mais cela semble assez tiré par les cheveux pour un petit script de maintenance. Je suis totalement d'accord sur le contrôle de version.
Adam Matan
3
S. Lott a raison. Gardez toujours les données et le code séparés, sauf si les données ne sont pas transitoires. Par exemple, si vous avez des icônes, ce sont des données, mais ce n'est pas transitoire, et il est logique de les considérer par rapport au bundle de logiciels (quoi que cela signifie)
Stefano Borini
5
@Udi Pasmon: Pas du tout exagéré. Ce sont les «petits scripts de maintenance» qui posent de sérieux problèmes aux organisations. Dans des années, ce "petit script de maintenance" et ses enfants, ses dérivations et ses fichiers de données seront un cauchemar à démêler et à réimplémenter. Gardez les données aussi loin que possible du code - passez des paramètres pour tout - ne supposez rien.
S.Lott
1
+1 Je pensais que je voulais faire comme l'OP, mais après avoir lu vos conseils, j'ai plutôt modifié mon script. Maintenant, il faut un paramètre pour spécifier l'emplacement d'un fichier journal.
Iain Samuel McLean Elder
+1. Il est plus facile de créer un package (rpm) pour un script python si les répertoires de données peuvent être personnalisés facilement.
jfs
18

Changez votre commande crontab en

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

Le (...)démarre un sous-shell que votre crond exécute en une seule commande. Le || exit 1provoque l'échec de votre tâche cron au cas où le répertoire ne serait pas disponible.

Bien que les autres solutions puissent être plus élégantes à long terme pour vos scripts spécifiques, mon exemple pourrait toujours être utile dans les cas où vous ne pouvez pas modifier le programme ou la commande que vous souhaitez exécuter.

Ruud Althuizen
la source
1
C'est une solution extrêmement solide. Je me retrouve généralement à éditer les réponses d'autres personnes pour ajouter des éléments tels que || exit 1. C'est rafraîchissant de voir ça. Bien que je cd /home/udi/foo/ && ./bar.py
doive
2
@BrunoBronosky Avec l'explicite, exit 1votre crond sera informé d'une erreur et, dans la plupart des cas, enverra une notification par e-mail de l'échec.
Ruud Althuizen