python: cambie el directorio de trabajo de los scripts al directorio propio del script

171

Ejecuto un shell de Python desde crontab cada minuto:

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

/home/udi/footiene algunos subdirectorios necesarios, como /home/udi/foo/logy /home/udi/foo/config, que se /home/udi/foo/bar.pyrefiere a.

El problema es que crontabejecuta el script desde un directorio de trabajo diferente, por lo que ./log/bar.logfalla al intentar abrirlo .

¿Hay una buena manera de decirle al script que cambie el directorio de trabajo al propio directorio del script? Me gustaría una solución que funcione para cualquier ubicación de script, en lugar de decirle explícitamente dónde está.

EDITAR:

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

Fue la solución elegante más compacta. Gracias por sus respuestas y explicaciones!

Adam Matan
fuente
no relacionado con crontabel caso de uso: ambos sys.argv[0]y __file__fallan si el script se ejecuta usando execfile(); inspectbasada en solución podría usarse en su lugar.
jfs

Respuestas:

206

Esto cambiará su directorio de trabajo actual a para que funcione abrir rutas relativas:

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

Sin embargo, usted preguntó cómo cambiar a cualquier directorio en el que se encuentre su script Python, incluso si no sabe qué directorio será cuando esté escribiendo su script. Para hacer esto, puede usar las os.pathfunciones:

import os

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

Esto toma el nombre de archivo de su script, lo convierte en una ruta absoluta, luego extrae el directorio de esa ruta, luego cambia a ese directorio.

Eli Courtwright
fuente
3
Es igual a codificar el directorio.
Ikke
2
Si lo está ejecutando desde un enlace simbólico, esto no funcionará. Usar en __file__lugar de sys.argv[0].
Chris Down
1
¿Por qué el paso abspath? ¿Por qué no simplemente os.chdir(os.path.dirname(__file__))?
Coronel Panic
8
__file__falla en programas "congelados" (creados con py2exe, PyInstaller, cx_Freeze). sys.argv[0]trabajos. @ChrisDown: si desea seguir enlaces simbólicos; os.path.realpath()puede ser usado.
jfs
3
@EliCourtwright Si aún __file__no es una ruta absoluta y el usuario ha cambiado el directorio de trabajo, os.path.abspathfallará de todos modos.
Arthur Tacca
45

Puede obtener una versión más corta usando sys.path[0].

os.chdir(sys.path[0])

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

Como se inicializó al iniciar el programa, el primer elemento de esta lista path[0]es el directorio que contiene el script que se utilizó para invocar al intérprete de Python

xverges
fuente
23

No hagas esto.

Sus scripts y sus datos no deben combinarse en un directorio grande. Poner su código en alguna ubicación conocida ( site-packageso /var/opt/udio algo así) separado de sus datos. Use un buen control de versiones en su código para asegurarse de que tiene versiones actuales y anteriores separadas entre sí para que pueda recurrir a versiones anteriores y probar versiones futuras.

En pocas palabras: no mezcle código y datos.

Los datos son preciosos. El código va y viene.

Proporcione el directorio de trabajo como un valor de argumento de línea de comando. Puede proporcionar un valor predeterminado como variable de entorno. No lo deduzca (ni lo adivine)

Conviértalo en un valor de argumento requerido y haga esto.

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

No "asuma" un directorio basado en la ubicación de su software. No funcionará bien a la larga.

S.Lott
fuente
9
Creo que tiene razón sobre la separación de código y datos para paquetes de software grandes, pero parece muy descabellado para un pequeño script de mantenimiento. Estoy totalmente de acuerdo con el control de versiones.
Adam Matan
3
S. Lott tiene razón. Mantenga siempre los datos y el código separados, a menos que los datos no sean transitorios. Por ejemplo, si usted tiene iconos, que son los datos, pero no es transitoria, y tiene sentido para considerarlo en relación con el paquete de software (lo que significa)
Stefano Borini
55
@Udi Pasmon: No es descabellado en absoluto. Son las "pequeñas secuencias de comandos de mantenimiento" las que causan graves problemas a las organizaciones. Dentro de unos años, este "pequeño script de mantenimiento" y sus archivos secundarios y de derivaciones serán una pesadilla para desenredar y reimplementar. Mantenga los datos lo más lejos posible del código, pase parámetros para todo, no asuma nada.
S.Lott
1
+1 Pensé que quería hacer como OP, pero después de leer tu consejo, modifiqué mi script. Ahora requiere un parámetro para especificar la ubicación de un archivo de registro.
Iain Samuel McLean Élder
+1. Es más fácil crear un paquete (rpm) para un script de Python si los directorios de datos se pueden personalizar fácilmente.
jfs
18

Cambie su comando crontab a

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

El (...)comienza una sub-shell que ejecuta sus crond como un solo comando. Esto || exit 1hace que su cronjob falle en caso de que el directorio no esté disponible.

Aunque las otras soluciones pueden ser más elegantes a largo plazo para sus scripts específicos, mi ejemplo aún podría ser útil en los casos en que no pueda modificar el programa o el comando que desea ejecutar.

Ruud Althuizen
fuente
1
Esta es una solución extremadamente sólida. Por lo general, me encuentro editando las respuestas de otras personas para agregar cosas como el || exit 1. Es refrescante ver esto. Aunque tengo que preguntarme por qué no lo harías simplementecd /home/udi/foo/ && ./bar.py
Bruno Bronosky
2
@BrunoBronosky Con el explícito, exit 1su crond será notificado de un error y, en la mayoría de los casos, enviará una notificación por correo electrónico del error.
Ruud Althuizen