python: Ubah direktori kerja skrip ke direktori skrip itu sendiri

171

Saya menjalankan shell python dari crontab setiap menit:

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

/home/udi/foomemiliki beberapa subdirektori yang diperlukan, seperti /home/udi/foo/logdan /home/udi/foo/config, yang /home/udi/foo/bar.pymerujuk pada.

Masalahnya adalah crontabmenjalankan skrip dari direktori kerja yang berbeda, jadi mencoba membuka ./log/bar.loggagal.

Apakah ada cara yang bagus untuk memberitahu skrip untuk mengubah direktori kerja ke direktori skrip sendiri? Saya lebih suka solusi yang akan bekerja untuk lokasi skrip apa pun, daripada secara eksplisit memberitahu skrip di mana skrip itu berada.

EDIT:

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

Merupakan solusi elegan paling kompak. Terima kasih atas jawaban dan penjelasan Anda!

Adam Matan
sumber
tidak terkait dengan crontabuse-case: keduanya sys.argv[0]dan __file__gagal jika skrip dijalankan menggunakan execfile(); inspectsolusi berbasis bisa digunakan sebagai gantinya.
jfs

Jawaban:

206

Ini akan mengubah direktori kerja Anda saat ini sehingga membuka jalur relatif akan berfungsi:

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

Namun, Anda bertanya bagaimana cara mengubah ke direktori apa pun skrip Python Anda berada, bahkan jika Anda tidak tahu direktori apa yang akan terjadi ketika Anda sedang menulis skrip Anda. Untuk melakukan ini, Anda dapat menggunakan os.pathfungsi:

import os

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

Ini mengambil nama file skrip Anda, mengonversinya menjadi jalur absolut, lalu mengekstrak direktori jalur itu, lalu berubah ke direktori itu.

Eli Courtwright
sumber
3
Sama dengan hardcoding direktori.
Ikke
2
Jika Anda menjalankannya dari symlink, ini tidak akan berfungsi. Gunakan __file__sebagai ganti sys.argv[0].
Chris Down
1
Mengapa abspath melangkah? Kenapa tidak sederhana saja os.chdir(os.path.dirname(__file__))?
Kolonel Panic
8
__file__gagal dalam program "beku" (dibuat menggunakan py2exe, PyInstaller, cx_Freeze). sys.argv[0]bekerja. @ChrisDown: Jika Anda ingin mengikuti symlinks; os.path.realpath()bisa digunakan.
jfs
3
@EliCourtwright Jika __file__belum menjadi path absolut, dan pengguna telah mengubah direktori yang berfungsi, maka os.path.abspathakan tetap gagal.
Arthur Tacca
45

Anda bisa mendapatkan versi yang lebih pendek dengan menggunakan sys.path[0].

os.chdir(sys.path[0])

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

Seperti diinisialisasi pada saat startup program, item pertama dari daftar ini path[0],, adalah direktori yang berisi skrip yang digunakan untuk memanggil juru bahasa Python

xverges
sumber
23

Jangan lakukan ini.

Skrip dan data Anda tidak boleh dihaluskan menjadi satu direktori besar. Menaruh kode Anda di beberapa lokasi yang diketahui ( site-packagesatau /var/opt/udiatau sesuatu) terpisah dari data Anda. Gunakan kontrol versi yang baik pada kode Anda untuk memastikan bahwa Anda memiliki versi saat ini dan sebelumnya terpisah satu sama lain sehingga Anda dapat kembali ke versi sebelumnya dan menguji versi yang akan datang.

Intinya: Jangan menyatukan kode dan data.

Data sangat berharga. Kode datang dan pergi.

Berikan direktori yang berfungsi sebagai nilai argumen baris perintah. Anda dapat memberikan default sebagai variabel lingkungan. Jangan menyimpulkannya (atau menebaknya)

Jadikan nilai argumen yang diperlukan dan lakukan ini.

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

Jangan "menganggap" direktori berdasarkan lokasi perangkat lunak Anda. Ini tidak akan berhasil dengan baik dalam jangka panjang.

S.Lott
sumber
9
Saya pikir Anda benar tentang memisahkan kode dan data untuk paket perangkat lunak besar, tetapi tampaknya terlalu jauh untuk skrip pemeliharaan kecil. Saya setuju sepenuhnya tentang kontrol versi.
Adam Matan
3
S. Lott benar. Selalu pisahkan data dan kode, kecuali data tidak bersifat sementara. Misalnya, jika Anda memiliki ikon, yaitu data, tapi itu tidak fana, dan masuk akal untuk mempertimbangkan relatif terhadap bundel software (apa pun artinya)
Stefano Borini
5
@Udi Pasmon: Tidak dibuat-buat sama sekali. Itu adalah "skrip pemeliharaan kecil" yang membuat organisasi mendapat masalah besar. Bertahun-tahun dari sekarang, ini "skrip pemeliharaan kecil" dan itu anak-anak dan derivasi dan file data akan menjadi mimpi buruk untuk diurai dan diimplementasikan kembali. Simpan data sejauh mungkin dari kode - berikan parameter untuk semuanya - anggap tidak ada.
S.Lott
1
+1 Saya pikir saya ingin melakukan sebagai OP, tetapi setelah membaca saran Anda, saya malah mengubah skrip saya. Sekarang diperlukan parameter untuk menentukan lokasi file log.
Iain Samuel McLean Penatua
+1. Lebih mudah untuk membuat paket (rpm) untuk skrip python jika direktori data dapat dikustomisasi dengan mudah.
jfs
18

Ubah perintah crontab Anda ke

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

The (...)dimulai sub-shell yang mengeksekusi crond Anda sebagai satu perintah. The || exit 1menyebabkan cronjob Anda gagal dalam hal ini direktori tidak tersedia.

Meskipun solusi lain mungkin lebih elegan dalam jangka panjang untuk skrip spesifik Anda, contoh saya masih bisa berguna dalam kasus di mana Anda tidak dapat memodifikasi program atau perintah yang ingin Anda jalankan.

Ruud Althuizen
sumber
1
Ini adalah solusi yang sangat bagus. Saya biasanya menemukan diri saya mengedit jawaban orang lain untuk menambahkan hal - hal seperti || exit 1. Sangat menyegarkan melihat ini. Meskipun saya harus bertanya-tanya mengapa Anda tidak melakukannyacd /home/udi/foo/ && ./bar.py
Bruno Bronosky
2
@BrunoBronosky Dengan eksplisit, exit 1crond Anda akan diberi tahu tentang kesalahan, dan dalam kebanyakan kasus akan mengirim pemberitahuan email tentang kegagalan tersebut.
Ruud Althuizen