python: Thay đổi thư mục làm việc của tập lệnh thành thư mục riêng của tập lệnh


171

Tôi chạy một vỏ trăn từ crontab mỗi phút:

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

/home/udi/foocó một số thư mục con cần thiết, như /home/udi/foo/log/home/udi/foo/config, trong đó /home/udi/foo/bar.pyđề cập đến.

Vấn đề là crontabchạy tập lệnh từ một thư mục làm việc khác, vì vậy cố gắng mở ./log/bar.logthất bại.

Có cách nào hay để nói kịch bản thay đổi thư mục làm việc thành thư mục riêng của tập lệnh không? Tôi sẽ thích một giải pháp sẽ hoạt động cho bất kỳ vị trí tập lệnh nào, thay vì nói rõ ràng kịch bản đó ở đâu.

BIÊN TẬP:

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

Là giải pháp thanh lịch nhỏ gọn nhất. Cảm ơn câu trả lời và giải thích của bạn!


không liên quan đến crontabtrường hợp sử dụng: cả hai sys.argv[0]__file__thất bại nếu tập lệnh được chạy bằng cách sử dụng execfile(); inspectgiải pháp dựa trên có thể được sử dụng thay thế.
jfs

Câu trả lời:


206

Điều này sẽ thay đổi thư mục làm việc hiện tại của bạn để mở các đường dẫn tương đối sẽ hoạt động:

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

Tuy nhiên, bạn đã hỏi làm thế nào để thay đổi vào bất kỳ thư mục nào mà tập lệnh Python của bạn được đặt, ngay cả khi bạn không biết thư mục đó sẽ là gì khi bạn viết tập lệnh của mình. Để làm điều này, bạn có thể sử dụng các os.pathchức năng:

import os

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

Điều này lấy tên tệp của tập lệnh của bạn, chuyển đổi nó thành một đường dẫn tuyệt đối, sau đó trích xuất thư mục của đường dẫn đó, sau đó thay đổi vào thư mục đó.


3
Bằng mã hóa thư mục.
Ikke

2
Nếu bạn đang chạy nó từ một liên kết tượng trưng, ​​điều này sẽ không hoạt động. Sử dụng __file__thay vì sys.argv[0].
Chris Down

1
Tại sao bước abspath? Tại sao không đơn giản os.chdir(os.path.dirname(__file__))?
Đại tá Panic

8
__file__không thành công trong các chương trình "đóng băng" (được tạo bằng py2exe, PyInstaller, cx_Freeze). sys.argv[0]làm. @ChrisDown: Nếu bạn muốn theo liên kết tượng trưng; os.path.realpath()có thể được sử dụng.
jfs

3
@EliCourtwright Nếu __file__chưa phải là một đường dẫn tuyệt đối và người dùng đã thay đổi thư mục làm việc, thì os.path.abspathdù sao cũng sẽ thất bại.
Arthur Tacca

45

Bạn có thể có được một phiên bản ngắn hơn bằng cách sử dụng sys.path[0].

os.chdir(sys.path[0])

Từ http://docs.python.org/l Library / sys.html # sys.path

Như được khởi tạo khi khởi động chương trình, mục đầu tiên của danh sách này path[0], là thư mục chứa tập lệnh được sử dụng để gọi trình thông dịch Python


23

Đừng làm điều này.

Tập lệnh và dữ liệu của bạn không nên được trộn vào một thư mục lớn. Đặt mã của bạn trong một số vị trí đã biết ( site-packageshoặc /var/opt/udihoặc một cái gì đó) tách rời khỏi dữ liệu của bạn. Sử dụng kiểm soát phiên bản tốt trên mã của bạn để đảm bảo rằng bạn có các phiên bản hiện tại và trước đó tách biệt với nhau để bạn có thể quay lại các phiên bản trước và thử nghiệm các phiên bản trong tương lai.

Dòng dưới cùng: Không trộn lẫn mã và dữ liệu.

Dữ liệu là quý giá. Mã đến rồi đi.

Cung cấp thư mục làm việc như một giá trị đối số dòng lệnh. Bạn có thể cung cấp một mặc định như một biến môi trường. Đừng suy diễn nó (hoặc đoán nó)

Làm cho nó một giá trị đối số cần thiết và làm điều này.

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

Không "giả sử" một thư mục dựa trên vị trí của phần mềm của bạn. Nó sẽ không hoạt động tốt trong thời gian dài.


9
Tôi nghĩ rằng bạn đúng về việc tách mã và dữ liệu cho các gói phần mềm lớn, nhưng có vẻ như nó khá xa vời đối với một tập lệnh bảo trì nhỏ. Tôi hoàn toàn đồng ý về việc kiểm soát phiên bản.
Adam Matan

3
S. Lott đã đúng. Luôn giữ dữ liệu và mã riêng biệt, trừ khi dữ liệu không nhất thời. Ví dụ: nếu bạn có các biểu tượng, đó là dữ liệu, nhưng nó không phải là nhất thời và nên xem xét nó có liên quan đến gói phần mềm (bất kể điều đó có nghĩa là gì)
Stefano Borini

5
@Udi Pasmon: Không xa lắm đâu. Đó là "các kịch bản bảo trì nhỏ" khiến các tổ chức gặp rắc rối sâu sắc. Nhiều năm sau, "kịch bản bảo trì nhỏ" này và đó là trẻ em và các tệp dữ liệu và dữ liệu sẽ là một cơn ác mộng đối với việc gỡ rối và thực hiện lại. Giữ dữ liệu càng xa mã càng tốt - truyền tham số cho mọi thứ - không giả sử gì.
S.Lott

1
+1 Tôi nghĩ rằng tôi muốn làm OP, nhưng sau khi đọc lời khuyên của bạn, thay vào đó tôi đã sửa đổi kịch bản của mình. Bây giờ nó yêu cầu một tham số để xác định vị trí của tệp nhật ký.
Iain Samuel McLean Elder

+1. Việc tạo một gói (vòng / phút) cho tập lệnh python sẽ dễ dàng hơn nếu các thư mục dữ liệu có thể được tùy chỉnh dễ dàng.
jfs

18

Thay đổi lệnh crontab của bạn thành

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

Khởi động (...)một lớp vỏ con mà crond của bạn thực thi như một lệnh đơn. Các || exit 1nguyên nhân cronjob của bạn không thành công trong trường hợp thư mục không có sẵn.

Mặc dù về lâu dài các giải pháp khác có thể thanh lịch hơn đối với các tập lệnh cụ thể của bạn, ví dụ của tôi vẫn có thể hữu ích trong trường hợp bạn không thể sửa đổi chương trình hoặc lệnh mà bạn muốn thực thi.


1
Đây là một giải pháp cực kỳ tốt. Tôi thường thấy mình chỉnh sửa câu trả lời của người khác để thêm những thứ như || exit 1. Thật sảng khoái khi thấy điều này. Mặc dù tôi phải tự hỏi tại sao bạn sẽ không làmcd /home/udi/foo/ && ./bar.py
Bruno Bronosky

2
@BrunoBronosky Với exit 1thông báo rõ ràng, crond của bạn sẽ được thông báo về lỗi và trong hầu hết các trường hợp sẽ gửi thông báo qua email về lỗi này.
Ruud Althuizen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.