Xác định xem một thư mục có thể ghi được hay không


101

Cách tốt nhất trong Python để xác định xem một thư mục có thể ghi được cho người dùng thực thi tập lệnh hay không? Vì điều này có thể sẽ liên quan đến việc sử dụng mô-đun hệ điều hành, tôi nên đề cập rằng tôi đang chạy nó trong môi trường * nix.

Câu trả lời:


185

Mặc dù những gì Christophe đề xuất là một giải pháp Pythonic hơn, mô-đun os có chức năng os.access để kiểm tra quyền truy cập:

os.access('/path/to/folder', os.W_OK) # W_OK để viết, R_OK để đọc, v.v.


4
Tùy thuộc vào tình huống, "dễ dàng hơn để yêu cầu sự tha thứ" không phải là cách tốt nhất, ngay cả trong Python. Đôi khi, bạn nên "xin phép" như với phương thức os.access () đã đề cập, chẳng hạn khi xác suất gặp lỗi cao.
mjv

53
Kiểm tra một thư mục chỉ cho bit ghi là không đủ nếu bạn muốn ghi tệp vào thư mục. Bạn cũng sẽ cần kiểm tra bit thực thi nếu bạn muốn ghi vào thư mục. os.access ( '/ path / to / thư mục', os.W_OK | os.X_OK) Với os.W_OK bởi bản thân bạn chỉ có thể xóa các thư mục (và chỉ nếu thư mục đó là trống)
fthinker

4
Một điểm khác của os.access()nó là nó kiểm tra bằng cách sử dụng UID thực và GID chứ không phải những cái hiệu quả . Điều này có thể gây ra sự kỳ lạ trong môi trường SUID / SGID. ('nhưng tập lệnh chạy setuid root, tại sao nó không thể ghi vào tệp?')
Alexios

5
Có thể một chương trình chỉ muốn biết mà không cần thực sự viết. Nó có thể chỉ muốn thay đổi giao diện và / hoặc hành vi của GUI theo thuộc tính. Trong trường hợp đó, tôi sẽ không coi việc viết và xóa một tệp chỉ như một bài kiểm tra là điều khó hiểu.
Bachsau

1
Vừa thử nghiệm trên một mạng chia sẻ Windows. os.access(dirpath, os.W_OK | os.X_OK)trả về True ngay cả khi tôi không có quyền ghi.
iamanigeeit

69

Có vẻ lạ khi đề xuất điều này, nhưng một thành ngữ Python phổ biến là

Xin tha thứ thì dễ hơn xin phép

Sau thành ngữ đó, người ta có thể nói:

Hãy thử ghi vào thư mục được đề cập và bắt lỗi nếu bạn không có quyền làm như vậy.


5
+1 Python hay không, đây thực sự là cách đáng tin cậy nhất để kiểm tra quyền truy cập.
John Knoeller

5
Điều này cũng xử lý các lỗi khác có thể xảy ra khi ghi vào đĩa - chẳng hạn như không còn dung lượng trống trên đĩa. Đó là sức mạnh của cố gắng .. bạn không cần phải nhớ tất cả những gì có thể đi sai ;-)
Jochen Ritzel

4
Cảm ơn các bạn. Quyết định sử dụng os.access vì tốc độ là một yếu tố quan trọng trong những gì tôi đang làm ở đây mặc dù tôi chắc chắn có thể hiểu được giá trị của việc "xin tha thứ thì dễ hơn xin phép." ;)
đèn chiếu sáng

4
Đó là một IDIO tuyệt vời ... m - đặc biệt là khi kết hợp với một thành ngữ khác except: pass- theo cách này, bạn có thể luôn lạc quan và nghĩ về bản thân. / mỉa mai tắt. Bây giờ tại sao tôi lại muốn, chẳng hạn như cố gắng ghi một cái gì đó vào mọi thư mục trong hệ thống tệp của tôi, để tạo ra một danh sách các vị trí có thể ghi?
Tomasz Gandor

4
Có thể một chương trình chỉ muốn biết mà không cần thực sự viết. Nó có thể chỉ muốn thay đổi giao diện và / hoặc hành vi của GUI theo thuộc tính. Trong trường hợp đó, tôi sẽ không coi việc viết và xóa một tệp chỉ như một bài kiểm tra là điều khó hiểu.
Bachsau

19

Giải pháp của tôi bằng cách sử dụng tempfilemô-đun:

import tempfile
import errno

def isWritable(path):
    try:
        testfile = tempfile.TemporaryFile(dir = path)
        testfile.close()
    except OSError as e:
        if e.errno == errno.EACCES:  # 13
            return False
        e.filename = path
        raise
    return True

Cập nhật: Sau khi kiểm tra lại mã trên Windows, tôi thấy rằng thực sự có sự cố khi sử dụng tệp tạm thời ở đó, hãy xem vấn đề22107: mô-đun tempfile hiểu sai lỗi bị từ chối truy cập trên Windows . Trong trường hợp thư mục không thể ghi, mã bị treo trong vài giây và cuối cùng ném một IOError: [Errno 17] No usable temporary file name found. Có thể đây là những gì người dùng2171842 đã quan sát? Rất tiếc, sự cố hiện vẫn chưa được giải quyết vì vậy để xử lý điều này, lỗi cũng cần được khắc phục:

    except (OSError, IOError) as e:
        if e.errno == errno.EACCES or e.errno == errno.EEXIST:  # 13, 17

Sự chậm trễ tất nhiên vẫn còn trong những trường hợp này.


1
Tôi nghĩ rằng một trong những sử dụng tempfile là sạch hơn vì nó chắc chắn không để lại dư lượng.
châu chấu,

3
phương pháp này không hoạt động bằng cách sử dụng tempfile. nó chỉ hoạt động khi không có OSErrornghĩa là nó có quyền ghi / xóa. nếu không, nó sẽ không return Falsevì không có lỗi nào được trả lại và tập lệnh sẽ không tiếp tục thực thi hoặc thoát. không có gì được trả lại. nó chỉ bị mắc kẹt ở dòng đó. tuy nhiên, việc tạo tệp không phải tạm thời như câu trả lời của khattam hoạt động cả khi quyền được phép hoặc bị từ chối. Cứu giúp?

10

Tình cờ tìm thấy chủ đề này để tìm kiếm ví dụ cho ai đó. Kết quả đầu tiên trên Google, xin chúc mừng!

Mọi người nói về cách thức hoạt động của Pythonic trong chủ đề này, nhưng không có ví dụ mã đơn giản nào? Của bạn đây, cho bất kỳ ai khác tình cờ:

import sys

filepath = 'C:\\path\\to\\your\\file.txt'

try:
    filehandle = open( filepath, 'w' )
except IOError:
    sys.exit( 'Unable to write to file ' + filepath )

filehandle.write("I am writing this text to the file\n")

Thao tác này cố gắng mở một trình xử lý tệp để ghi và thoát với lỗi nếu tệp được chỉ định không thể được ghi vào: Cách này dễ đọc hơn nhiều và là cách tốt hơn nhiều so với việc kiểm tra trước trên đường dẫn tệp hoặc thư mục , vì nó tránh được các điều kiện về chủng tộc; các trường hợp mà tệp trở nên không thể ghi được giữa thời gian bạn chạy kiểm tra trước và khi bạn thực sự cố gắng ghi vào tệp.


1
Điều này áp dụng cho một tệp, không phải thư mục, đó là những gì OP yêu cầu. Bạn có thể có một tệp trong một thư mục và có thư mục không thể ghi được nhưng chính tệp đó, nếu tệp đã tồn tại. Điều này có thể quan trọng trong quản trị hệ thống, ví dụ như bạn đang tạo các tệp nhật ký mà bạn muốn tồn tại nhưng không muốn mọi người sử dụng thư mục nhật ký cho không gian tạm thời.
Mike S

... và thực sự là tôi đã bỏ phiếu, điều mà bây giờ tôi nghĩ là một sai lầm. Có những vấn đề, như Rohaq đã đề cập, với điều kiện chủng tộc. Có những vấn đề khác trên các nền tảng khác nhau mà bạn có thể kiểm tra thư mục và nó có vẻ có thể ghi được, nhưng thực tế thì không. Thực hiện kiểm tra thư mục có thể ghi trên nhiều nền tảng khó hơn trông thấy. Vì vậy, miễn là bạn nhận thức được các vấn đề, đây có thể là một kỹ thuật tốt. Tôi đã nhìn nó từ một góc độ quá UNIX-y, đó là sai lầm của tôi. Ai đó chỉnh sửa câu trả lời này để tôi có thể xóa -1 của mình.
Mike S

Tôi đã chỉnh sửa nó, trong trường hợp bạn muốn xóa -1 :) Và vâng, việc kiểm tra thư mục đa nền tảng có thể phức tạp hơn, nhưng nhìn chung bạn đang tìm cách tạo / ghi vào một tệp trong thư mục đó - trong trường hợp đó ví dụ tôi đã đưa ra vẫn nên áp dụng. Nếu một số vấn đề liên quan đến quyền thư mục xuất hiện, nó vẫn sẽ xuất hiện IOError khi cố gắng mở tệp xử lý.
Rohaq

Tôi đã xóa phiếu phản đối của mình. Xin lỗi về điều đó, và cảm ơn vì sự đóng góp của bạn.
Mike S

Đừng lo lắng, những người đặt câu hỏi câu trả lời luôn được hoan nghênh!
Rohaq

9

Nếu bạn chỉ quan tâm đến quyền của tệp, os.access(path, os.W_OK)hãy làm những gì bạn yêu cầu. Thay vào đó, nếu bạn muốn biết liệu bạn có thể ghi vào thư mục, open()một tệp thử nghiệm để ghi (nó không nên tồn tại trước đó), hãy nắm bắt và kiểm tra bất kỳ IOErrortệp nào và xóa tệp kiểm tra sau đó.

Nói chung, để tránh các cuộc tấn công TOCTOU (chỉ có vấn đề nếu tập lệnh của bạn chạy với các đặc quyền nâng cao - suid hoặc cgi hoặc tương tự), bạn không nên thực sự tin tưởng vào các thử nghiệm trước thời hạn này, nhưng hãy thả priv, thực hiện open()và mong đợi cái IOError.


7

Kiểm tra các bit chế độ:

def isWritable(name):
  uid = os.geteuid()
  gid = os.getegid()
  s = os.stat(dirname)
  mode = s[stat.ST_MODE]
  return (
     ((s[stat.ST_UID] == uid) and (mode & stat.S_IWUSR)) or
     ((s[stat.ST_GID] == gid) and (mode & stat.S_IWGRP)) or
     (mode & stat.S_IWOTH)
     )

4
Giải pháp này chỉ dành cho Unix.
Björn Lindqvist

4

Đây là thứ mà tôi đã tạo dựa trên câu trả lời của ChristopheD:

import os

def isWritable(directory):
    try:
        tmp_prefix = "write_tester";
        count = 0
        filename = os.path.join(directory, tmp_prefix)
        while(os.path.exists(filename)):
            filename = "{}.{}".format(os.path.join(directory, tmp_prefix),count)
            count = count + 1
        f = open(filename,"w")
        f.close()
        os.remove(filename)
        return True
    except Exception as e:
        #print "{}".format(e)
        return False

directory = "c:\\"
if (isWritable(directory)):
    print "directory is writable"
else:
    print "directory is not writable"

3
 if os.access(path_to_folder, os.W_OK) is not True:
            print("Folder not writable")
 else :
            print("Folder writable")

có thể tìm thấy thêm thông tin về quyền truy cập ở đây


2
Về cơ bản, đây là một bản sao câu trả lời của Max Shawabkeh với một chút giấy bọc xung quanh nó. Làm cho nó trở thành một bản sao dán nhanh chóng nhưng ý tưởng tốt hơn là nên thêm nó vào bài đăng gốc của Max.
Jorrick Sleijster

1

Tôi gặp phải nhu cầu tương tự này trong khi thêm một đối số qua argparse. Phần mềm tích hợp type=FileType('w')sẽ không hoạt động đối với tôi khi tôi đang tìm kiếm một thư mục. Tôi đã viết ra phương pháp của riêng mình để giải quyết vấn đề của tôi. Đây là kết quả với đoạn mã argparse.

#! /usr/bin/env python
import os
import argparse

def writable_dir(dir):
    if os.access(dir, os.W_OK) and os.path.isdir(dir):
        return os.path.abspath(dir)
    else:
        raise argparse.ArgumentTypeError(dir + " is not writable or does not exist.")

parser = argparse.ArgumentParser()
parser.add_argument("-d","--dir", type=writable_dir(), default='/tmp/',
    help="Directory to use. Default: /tmp")
opts = parser.parse_args()

Điều đó dẫn đến những điều sau:

$ python dir-test.py -h
usage: dir-test.py [-h] [-d DIR]

optional arguments:
  -h, --help         show this help message and exit
  -d DIR, --dir DIR  Directory to use. Default: /tmp

$ python dir-test.py -d /not/real
usage: dir-test.py [-h] [-d DIR]
dir-test.py: error: argument -d/--dir: /not/real is not writable or does not exist.

$ python dir-test.py -d ~

Tôi quay lại và thêm print opts.dir vào cuối và mọi thứ dường như hoạt động như mong muốn.


0

Nếu bạn cần kiểm tra sự cho phép của người dùng khác (vâng, tôi nhận thấy điều này mâu thuẫn với câu hỏi, nhưng có thể hữu ích cho ai đó), bạn có thể làm điều đó thông qua pwdmô-đun và các bit chế độ của thư mục.

Tuyên bố từ chối trách nhiệm - không hoạt động trên Windows, vì nó không sử dụng mô hình quyền POSIX (và pwdmô-đun không có sẵn ở đó), ví dụ - giải pháp chỉ dành cho hệ thống * nix.

Lưu ý rằng một thư mục phải có tất cả 3 bit được thiết lập - Đọc, Ghi và eXecute.
Ok, R không phải là một bắt buộc tuyệt đối, nhưng nếu bạn không thể liệt kê các mục trong thư mục (vì vậy bạn phải biết tên của chúng). Mặt khác, thực thi là hoàn toàn cần thiết - nếu người dùng không thể đọc các inodes của tệp; vì vậy ngay cả khi có W, không có X các tệp không thể được tạo hoặc sửa đổi. Giải thích chi tiết hơn tại liên kết này.

Cuối cùng, các chế độ có sẵn trong statmô-đun, mô tả của chúng ở inode (7) man .

Mã mẫu cách kiểm tra:

import pwd
import stat
import os

def check_user_dir(user, directory):
    dir_stat = os.stat(directory)

    user_id, group_id = pwd.getpwnam(user).pw_uid, pwd.getpwnam(user).pw_gid
    directory_mode = dir_stat[stat.ST_MODE]

    # use directory_mode as mask 
    if user_id == dir_stat[stat.ST_UID] and stat.S_IRWXU & directory_mode == stat.S_IRWXU:     # owner and has RWX
        return True
    elif group_id == dir_stat[stat.ST_GID] and stat.S_IRWXG & directory_mode == stat.S_IRWXG:  # in group & it has RWX
        return True
    elif stat.S_IRWXO & directory_mode == stat.S_IRWXO:                                        # everyone has RWX
        return True

    # no permissions
    return False
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.