python: Altere o diretório de trabalho dos scripts para o próprio diretório do script

171

Eu corro um shell python do crontab a cada minuto:

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

/home/udi/foopossui alguns subdiretórios necessários, como /home/udi/foo/loge /home/udi/foo/config, ao qual /home/udi/foo/bar.pyse refere.

O problema é que crontabo script é executado a partir de um diretório de trabalho diferente, portanto, a tentativa de abrir ./log/bar.logfalha.

Existe uma boa maneira de dizer ao script para alterar o diretório de trabalho para o próprio diretório do script? Gostaria de uma solução que funcionasse para qualquer local de script, em vez de dizer explicitamente onde está o script.

EDITAR:

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

Foi a solução elegante mais compacta. Obrigado por suas respostas e explicações!

Adam Matan
fonte
não relacionado ao crontabcaso de uso: both sys.argv[0]e __file__fail se o script for executado usando execfile(); inspectsolução baseada em dados poderia ser usada.
JFS

Respostas:

206

Isso mudará seu diretório de trabalho atual para, para que a abertura de caminhos relativos funcione:

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

No entanto, você perguntou como mudar para qualquer diretório em que seu script Python esteja localizado, mesmo que você não saiba qual diretório será esse quando estiver escrevendo seu script. Para fazer isso, você pode usar as os.pathfunções:

import os

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

Isso pega o nome do arquivo do seu script, converte-o para um caminho absoluto, extrai o diretório desse caminho e depois muda para esse diretório.

Eli Courtwright
fonte
3
É igual à codificação codificada do diretório.
226/09 Ikke
2
Se você estiver executando a partir de um link simbólico, isso não funcionará. Use em __file__vez de sys.argv[0].
Chris Down
1
Por que o passo do abspath? Por que não simplesmente os.chdir(os.path.dirname(__file__))?
Coronel Panic
8
__file__falha em programas "congelados" (criados usando py2exe, PyInstaller, cx_Freeze). sys.argv[0]trabalho. @ ChrisDown: Se você deseja seguir links simbólicos; os.path.realpath()poderia ser usado.
JFS
3
@EliCourtwright Se __file__ainda não é um caminho absoluto e o usuário alterou o diretório de trabalho, ele os.path.abspathfalhará de qualquer maneira.
Arthur Tacca
45

Você pode obter uma versão mais curta usando sys.path[0].

os.chdir(sys.path[0])

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

Conforme inicializado na inicialização do programa, o primeiro item desta lista path[0], é o diretório que contém o script usado para chamar o interpretador Python.

xverges
fonte
23

Não faça isso.

Seus scripts e seus dados não devem ser compactados em um grande diretório. Coloque seu código em algum local conhecido ( site-packagesou /var/opt/udiou algo assim) separar de seus dados. Use um bom controle de versão no seu código para garantir que as versões atual e anterior sejam separadas uma da outra, para que você possa voltar às versões anteriores e testar versões futuras.

Conclusão: não misture código e dados.

Dados são preciosos. O código vem e vai.

Forneça o diretório ativo como um valor do argumento da linha de comandos. Você pode fornecer um padrão como uma variável de ambiente. Não deduza (ou adivinhe)

Crie um valor de argumento necessário e faça isso.

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ão "assuma" um diretório com base na localização do seu software. Não vai funcionar bem a longo prazo.

S.Lott
fonte
9
Acho que você tem razão em separar código e dados para grandes pacotes de software, mas parece muito improvável para um pequeno script de manutenção. Eu concordo totalmente com o controle de versão.
14119 Adam Matan
3
S. Lott está certo. Sempre mantenha os dados e o código separados, a menos que os dados não sejam transitórios. Por exemplo, se você tiver ícones, que são dados, mas não é transitória, e faz sentido considerá-lo em relação ao pacote de software (whatever that means)
Stefano Borini
5
@Udi Pasmon: Não é exagero. São os "pequenos scripts de manutenção" que colocam as organizações em problemas profundos. Daqui a alguns anos, esse "pequeno script de manutenção" e seus filhos, derivações e arquivos de dados serão um pesadelo para desemaranhar e reimplementar. Mantenha os dados o mais longe possível do código - passe parâmetros para tudo - não assuma nada.
S.Lott
1
+1 Pensei que queria fazer o OP, mas depois de ler seus conselhos, modifiquei meu script. Agora, ele requer um parâmetro para especificar o local de um arquivo de log.
Iain Samuel McLean Elder
+1. É mais fácil criar um pacote (rpm) para um script python se os diretórios de dados puderem ser personalizados facilmente.
JFS
18

Mude seu comando crontab para

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

O (...)inicia um sub-shell que sua crond executa como um único comando. O || exit 1faz com que seu cronjob a falhar no caso de que o diretório não está disponível.

Embora as outras soluções possam ser mais elegantes a longo prazo para seus scripts específicos, meu exemplo ainda pode ser útil nos casos em que você não pode modificar o programa ou comando que deseja executar.

Ruud Althuizen
fonte
1
Esta é uma solução extremamente sólida. Normalmente, eu me pego editando respostas de outras pessoas para adicionar coisas como o || exit 1. É refrescante ver isso. Embora eu tenho que perguntar por que você não iria apenas fazercd /home/udi/foo/ && ./bar.py
de Bruno Bronosky
2
@BrunoBronosky Com o explícito, exit 1seu crond será notificado sobre um erro e, na maioria dos casos, enviará uma notificação por email da falha.
Ruud Althuizen 4/17/17