python: zmień katalog roboczy skryptów na własny katalog skryptu

171

Co minutę uruchamiam powłokę Pythona z crontaba:

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

/home/udi/fooma pewne niezbędne podkatalogi, takie jak /home/udi/foo/logi /home/udi/foo/config, do którego /home/udi/foo/bar.pyodwołuje się.

Problem polega na tym, że crontaburuchamia skrypt z innego katalogu roboczego, więc próba otwarcia ./log/bar.logkończy się niepowodzeniem.

Czy istnieje dobry sposób, aby powiedzieć skryptowi, aby zmienił katalog roboczy na własny katalog skryptu? Spodobało mi się rozwiązanie, które działałoby dla dowolnej lokalizacji skryptu, zamiast wyraźnie wskazywać skryptowi, gdzie on się znajduje.

EDYTOWAĆ:

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

Było najbardziej kompaktowym, eleganckim rozwiązaniem. Dziękuję za odpowiedzi i wyjaśnienia!

Adam Matan
źródło
niezwiązane z crontabprzypadkiem użycia: oba sys.argv[0]i __file__kończą się niepowodzeniem, jeśli skrypt jest uruchamiany przy użyciu execfile(); Zamiast tego można użyć inspectrozwiązania opartego na rozwiązaniach .
jfs

Odpowiedzi:

206

Spowoduje to zmianę bieżącego katalogu roboczego na tak, aby otwieranie ścieżek względnych działało:

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

Jednak zapytałeś, jak przejść do dowolnego katalogu, w którym znajduje się skrypt Pythona, nawet jeśli nie wiesz, który to będzie katalog podczas pisania skryptu. Aby to zrobić, możesz użyć os.pathfunkcji:

import os

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

Pobiera nazwę pliku skryptu, konwertuje go na ścieżkę bezwzględną, następnie wyodrębnia katalog z tej ścieżki, a następnie przechodzi do tego katalogu.

Eli Courtwright
źródło
3
To jest równe zakodowaniu katalogu.
Ikke
2
Jeśli uruchamiasz go z łącza symbolicznego, to nie zadziała. Użyj __file__zamiast sys.argv[0].
Chris Down,
1
Dlaczego krok abspath? Dlaczego nie po prostu os.chdir(os.path.dirname(__file__))?
Panika pułkownika
8
__file__nie działa w „zamrożonych” programach (utworzonych przy użyciu py2exe, PyInstaller, cx_Freeze). sys.argv[0]Pracuje. @ChrisDown: Jeśli chcesz śledzić linki symboliczne; os.path.realpath()może być użyty.
jfs
3
@EliCourtwright Jeśli __file__nie jest jeszcze ścieżką bezwzględną, a użytkownik zmienił katalog roboczy, i os.path.abspathtak zakończy się niepowodzeniem.
Arthur Tacca
45

Możesz uzyskać krótszą wersję, używając sys.path[0].

os.chdir(sys.path[0])

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

Zgodnie z inicjalizacją podczas uruchamiania programu, pierwszą pozycją na tej liście path[0]jest katalog zawierający skrypt, który został użyty do wywołania interpretera Pythona

xverges
źródło
23

Nie rób tego.

Twoje skrypty i dane nie powinny być łączone w jeden duży katalog. Umieścić swój kod w jakiejś znanej lokalizacji ( site-packageslub /var/opt/udiczy coś) oddzielone od danych. Używaj dobrej kontroli wersji w swoim kodzie, aby mieć pewność, że masz oddzielone od siebie wersje bieżące i poprzednie, dzięki czemu możesz wrócić do poprzednich wersji i przetestować przyszłe wersje.

Konkluzja: nie łącz kodu i danych.

Dane są cenne. Kod przychodzi i odchodzi.

Podaj katalog roboczy jako wartość argumentu wiersza polecenia. Możesz podać wartość domyślną jako zmienną środowiskową. Nie dedukuj tego (ani nie zgaduj)

Ustaw wymaganą wartość argumentu i zrób to.

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

Nie „zakładaj” katalogu na podstawie lokalizacji oprogramowania. Na dłuższą metę nie będzie dobrze działać.

S.Lott
źródło
9
Myślę, że masz rację co do oddzielania kodu i danych dla dużych pakietów oprogramowania, ale wydaje się to dość naciągane jak na mały skrypt konserwacyjny. Całkowicie zgadzam się z kontrolą wersji.
Adam Matan
3
S. Lott ma rację. Zawsze przechowuj dane i kod oddzielnie, chyba że dane nie są przejściowe. Na przykład, jeśli masz ikony, to znaczy dane, ale nie są one przejściowe i sensowne jest rozważenie tego w odniesieniu do pakietu oprogramowania (cokolwiek to znaczy)
Stefano Borini
5
@Udi Pasmon: Wcale nie naciągane. To właśnie „małe skrypty konserwacyjne” powodują poważne kłopoty organizacji. Za lata ten „mały skrypt konserwacyjny” i jego elementy podrzędne, wyprowadzenia i pliki danych będą koszmarem do wyplątania i ponownego wdrożenia. Trzymaj dane jak najdalej od kodu - przekazuj parametry dla wszystkiego - niczego nie zakładaj.
S.Lott,
1
+1 Myślałem, że chcę zrobić jako OP, ale po przeczytaniu twojej rady zamiast tego zmodyfikowałem mój skrypt. Teraz wymaga parametru określającego lokalizację pliku dziennika.
Iain Samuel McLean Elder
+1. Łatwiej jest utworzyć pakiet (rpm) dla skryptu w języku Python, jeśli katalogi danych można łatwo dostosować.
jfs
18

Zmień polecenie crontab na

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

(...)Rozpoczyna sub-shell, że crond wykonywany jako jednego polecenia. || exit 1Powoduje, że cron niepowodzenie w przypadku, że katalog jest niedostępny.

Chociaż inne rozwiązania mogą na dłuższą metę być bardziej eleganckie dla określonych skryptów, mój przykład może być nadal przydatny w przypadkach, gdy nie możesz zmodyfikować programu lub polecenia, które chcesz wykonać.

Ruud Althuizen
źródło
1
To niezwykle solidne rozwiązanie. Zwykle edytuję odpowiedzi innych osób, aby dodać takie rzeczy, jak || exit 1. To orzeźwiające widzieć to. Chociaż muszę się zastanawiać, dlaczego po prostu nie zrobiłeścd /home/udi/foo/ && ./bar.py
Bruno Bronosky
2
@BrunoBronosky Podając jawną informację, exit 1twój crond zostanie powiadomiony o błędzie, aw większości przypadków wyśle ​​e-mail z powiadomieniem o błędzie.
Ruud Althuizen