python: изменить рабочий каталог скриптов на собственный каталог скрипта

171

Я запускаю оболочку Python из crontab каждую минуту:

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

/home/udi/fooимеет несколько необходимых подкаталогов, вроде /home/udi/foo/logи /home/udi/foo/config, на которые /home/udi/foo/bar.pyссылается.

Проблема в том, что crontabскрипт запускается из другого рабочего каталога, поэтому попытка открыть его ./log/bar.logне удалась.

Есть ли хороший способ сказать сценарию изменить рабочий каталог на собственный каталог сценария? Я хотел бы найти решение, которое будет работать для любого местоположения сценария, а не явно указывать сценарию, где оно находится.

РЕДАКТИРОВАТЬ:

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

Было самым компактным элегантным решением. Спасибо за ваши ответы и объяснения!

Адам Матан
источник
несвязанный с crontabпрецедентом: оба sys.argv[0]и __file__терпят неудачу, если скрипт выполняется с помощью execfile(); inspectрешение может быть использовано вместо.
JFS

Ответы:

206

Это изменит ваш текущий рабочий каталог, чтобы открывать относительные пути:

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

Однако вы спросили, как перейти в любой каталог, в котором находится ваш скрипт Python, даже если вы не знаете, какой каталог будет при написании скрипта. Для этого вы можете использовать os.pathфункции:

import os

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

Это берет имя файла вашего скрипта, преобразует его в абсолютный путь, затем извлекает каталог этого пути, а затем изменяется в этот каталог.

Эли Кортрайт
источник
3
Равен жесткому кодированию каталога.
Икке
2
Если вы запускаете его по символической ссылке, это не будет работать. Используйте __file__вместо sys.argv[0].
Крис Даун
1
Почему шаг абспата? Почему не просто os.chdir(os.path.dirname(__file__))?
полковник Паник
8
__file__происходит сбой в «замороженных» программах (созданных с использованием py2exe, PyInstaller, cx_Freeze). sys.argv[0]работает. @ChrisDown: если вы хотите перейти по символическим ссылкам; os.path.realpath()может быть использован.
JFS
3
@EliCourtwright Если __file__это еще не абсолютный путь, и пользователь изменил рабочий каталог, то os.path.abspathвсе равно произойдет сбой.
Артур Такка
45

Вы можете получить более короткую версию, используя sys.path[0].

os.chdir(sys.path[0])

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

Как инициализируется при запуске программы, первым элементом этого списка path[0]является каталог, содержащий скрипт, который использовался для вызова интерпретатора Python.

xverges
источник
23

Не делай этого.

Ваши сценарии и ваши данные не должны быть объединены в один большой каталог. Поместите свой код в некотором известном месте ( site-packagesили /var/opt/udiили что - то) отдельно от ваших данных. Используйте хороший контроль версий в своем коде, чтобы убедиться, что у вас есть текущие и предыдущие версии, отделенные друг от друга, чтобы вы могли вернуться к предыдущим версиям и протестировать будущие версии.

Итог: не смешивайте код и данные.

Данные драгоценны. Код приходит и уходит.

Укажите рабочий каталог в качестве значения аргумента командной строки. Вы можете указать значение по умолчанию в качестве переменной среды. Не выводи это (или не догадывайся об этом)

Сделайте это обязательным значением аргумента и сделайте это.

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

Не «предполагайте» каталог, основанный на местоположении вашего программного обеспечения. Это не сработает в долгосрочной перспективе.

С. Лотт
источник
9
Я думаю, что вы правы в разделении кода и данных для больших пакетов программного обеспечения, но это кажется довольно надуманным для небольшого сценария обслуживания. Я полностью согласен с контролем версий.
Адам Матан
3
С. Лотт прав. Всегда храните данные и код отдельно, если только данные не являются временными. Например, если у вас есть значки, это данные, но они не являются временными, и имеет смысл рассмотреть их относительно пакета программного обеспечения (что бы это ни значило)
Стефано Борини
5
@ Udi Pasmon: Не надуманно. Это «небольшие сценарии обслуживания», которые доставляют организациям серьезные проблемы. Через несколько лет этот «небольшой сценарий обслуживания», его дочерние элементы, производные файлы и файлы данных станут кошмаром для распутывания и переопределения. Храните данные как можно дальше от кода - передавайте параметры для всего - ничего не предполагайте.
С.Лотт
1
+1 Я думал, что хочу сделать как ОП, но после прочтения вашего совета я вместо этого изменил свой сценарий. Теперь требуется параметр для указания местоположения файла журнала.
Иэн Сэмюэль Маклин, старейшина
+1. Проще создать пакет (rpm) для скрипта Python, если каталоги данных можно легко настроить.
JFS
18

Измените команду crontab на

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

(...)Начинает подоболочку , что ваш crond выполняется в качестве одной команды. || exit 1Вызывает ваш cronjob к сбою в случае, если каталог недоступен.

Хотя другие решения могут быть более элегантными в долгосрочной перспективе для ваших конкретных сценариев, мой пример может быть полезен в тех случаях, когда вы не можете изменить программу или команду, которую хотите выполнить.

Рууд Алтуизен
источник
1
Это чрезвычайно разумное решение. Я обычно нахожу себя редактирующим ответы других людей, чтобы добавить вещи как || exit 1. Приятно видеть это. Хотя мне непонятно, почему бы тебе просто не занятьсяcd /home/udi/foo/ && ./bar.py
Бруно Броноски
2
@BrunoBronosky С явным exit 1ваш crond будет уведомлен об ошибке, и в большинстве случаев отправит уведомление по электронной почте о сбое.
Рууд Алтуизен