【问题标题】:Determining Whether a Directory is Writeable确定目录是否可写
【发布时间】:2011-01-07 23:28:06
【问题描述】:

在 Python 中,确定目录是否可供执行脚本的用户写入的最佳方法是什么?由于这可能涉及使用 os 模块,我应该提到我在 *nix 环境下运行它。

【问题讨论】:

    标签: python file permissions directory operating-system


    【解决方案1】:

    这似乎很奇怪,但一个常见的 Python 习惯用法是

    请求原谅更容易 不是为了许可

    按照这个成语,人们可能会说:

    尝试写入有问题的目录,如果您没有这样做的权限,请捕获错误。

    【讨论】:

    • +1 Python 与否,这确实是最可靠的访问测试方法。
    • 这也会处理写入磁盘时可能发生的其他错误——例如没有剩余磁盘空间。这就是尝试的力量......你不需要记住所有可能出错的事情;-)
    • 谢谢大家。决定使用 os.access,因为速度是我在这里所做的一个重要因素,尽管我当然可以理解“请求宽恕比请求许可更容易”的优点。 ;)
    • 这是一个很棒的 IDIO...m - 尤其是与另一个成语 except: pass 结合使用时 - 这样您就可以始终保持乐观并高度评价自己。 /讽刺关闭。现在我为什么要这样做,例如尝试在我的文件系统中的每个目录中写入一些内容,以生成可写位置列表?
    • 也许程序只是想知道而不需要实际编写。它可能只想根据属性更改 GUI 的外观和/或行为。在那种情况下,我不会认为将文件写入和删除只是作为测试是 Pythonic。
    【解决方案2】:

    检查模式位:

    import os, stat
    
    def isWritable(dirname):
      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)
         )
    

    【讨论】:

    • 此解决方案仅适用于 Unix。
    【解决方案3】:

    虽然 Christophe 建议的是一个更 Pythonic 的解决方案,但 os 模块确实有 the os.access function 来检查访问:

    os.access('/path/to/folder', os.W_OK) # W_OK 用于写入,R_OK 用于读取等

    【讨论】:

    • 视情况而定,“更容易请求原谅”并不是最好的方法,即使在 Python 中也是如此。有时建议像提到的 os.access() 方法一样“请求许可”,例如当必须捕获错误的可能性很高时。
    • 如果要将文件写入目录,仅测试目录的写入位是不够的。如果要写入目录,还需要测试执行位。 os.access('/path/to/folder', os.W_OK | os.X_OK) 单独使用 os.W_OK 只能删除目录(且仅当该目录为空时)
    • os.access() 的另一个问题是它使用 real UID 和 GID,而不是 有效 进行检查。这可能会导致 SUID/SGID 环境中的怪异。 (‘但是脚本运行setuid root,为什么不能写入文件?’)
    • 也许程序只是想知道而不需要实际编写。它可能只想根据属性更改 GUI 的外观和/或行为。在那种情况下,我不会认为将文件写入和删除只是作为测试是 Pythonic。
    • 刚刚在 Windows 网络共享上进行了测试。 os.access(dirpath, os.W_OK | os.X_OK) 即使我没有写权限也返回 True。
    【解决方案4】:

    如果您只关心文件权限,os.access(path, os.W_OK) 应该满足您的要求。如果您想知道是否可以写入目录,open() 一个用于写入的测试文件(它不应该事先存在),捕获并检查任何IOError,然后清理之后测试文件。

    更一般地说,为了避免TOCTOU 攻击(只有当您的脚本以提升的权限运行时才会出现问题 - suid 或 cgi 左右),您不应该真正信任这些提前测试,而是放弃 privs,执行open(),并期待IOError

    【讨论】:

      【解决方案5】:

      这是我根据 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"
      

      【讨论】:

        【解决方案6】:

        偶然发现此线程正在寻找某人的示例。 Google 上的第一个结果,恭喜!

        人们在这个线程中谈论 Pythonic 的方式,但没有简单的代码示例?在这里,对于任何偶然发现的人:

        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")
        

        这会尝试打开一个文件句柄进行写入,如果指定的文件无法写入则退出并返回错误:这更容易阅读,并且是一种更好的方法,而不是对文件路径进行预检查或目录,因为它避免了竞争条件;在运行预检查和实际尝试写入文件之间文件变得不可写的情况。

        【讨论】:

        • 这适用于文件,而不是 OP 要求的目录。如果文件已经存在,您可以在目录中有一个文件,并且该目录不可写,但文件本身是可写的。这在系统管理中可能很重要,例如,您正在创建希望已经存在但不希望人们使用日志目录作为临时空间的日志文件。
        • ...实际上我投了反对票,我现在认为这是一个错误。正如 Rohaq 所提到的,比赛条件存在一些问题。在您可以测试目录的各种平台上还有其他问题,它看起来是可写的,但实际上不是。执行跨平台目录可写检查比看起来更难。因此,只要您意识到这些问题,这可能是一个很好的技术。我从一个太 UNIX-y 的角度来看它,这是我的错误。有人编辑这个答案,所以我可以删除我的 -1。
        • 我已经对其进行了编辑,以防您想删除 -1 :) 是的,跨平台目录检查可能会变得更加复杂,但通常您希望创建/写入该目录中的文件-在这种情况下,我给出的示例仍应适用。如果出现一些与目录权限相关的问题,尝试打开文件句柄时仍应抛出 IOError。
        • 我删除了我的反对票。很抱歉,感谢您的贡献。
        • 别担心,总是欢迎人们提问答案!
        【解决方案7】:

        我使用tempfile 模块的解决方案:

        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
        

        更新: 在 Windows 上再次测试代码后,我发现在那里使用 tempfile 时确实存在问题,请参阅issue22107: tempfile module misinterprets access denied error on Windows。 在不可写目录的情况下,代码会挂起几秒钟,最后抛出IOError: [Errno 17] No usable temporary file name found。也许这就是 user2171842 观察到的? 不幸的是,这个问题目前还没有解决,所以要处理这个问题,还需要捕获错误:

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

        那么,在这些情况下,延迟当然仍然存在。

        【讨论】:

        • 我认为使用 tempfile 的更干净,因为它肯定不会留下残差。
        • 此方法在使用 tempfile 时不起作用。它仅在没有OSError 时才有效,这意味着它有权写入/删除。否则不会return False,因为没有返回错误,脚本不会继续执行或退出。什么都没有返回。它只是停留在那条线上。但是,创建非临时文件(例如 khattam 的答案)在允许或拒绝许可时都有效。帮忙?
        • 这个也可以抛出OSError: [Errno 30] Read-only file system
        【解决方案8】:
         if os.access(path_to_folder, os.W_OK) is not True:
                    print("Folder not writable")
         else :
                    print("Folder writable")
        

        更多关于访问的信息可以找到它here

        【讨论】:

        • 这基本上是 Max Shawabkeh 答案的副本,并带有一个小包装。使其成为快速复制粘贴,但更好的主意是将其添加到 Max 的原始帖子中。
        【解决方案9】:

        如果您需要检查另一个用户的权限(是的,我意识到这与问题相矛盾,但可能对某人有用),您可以通过pwd 模块完成,和目录的模式位。

        免责声明 - 不适用于 Windows,因为它不使用 POSIX 权限模型(并且 pwd 模块不可用),例如- 仅适用于 *nix 系统的解决方案。

        请注意,目录必须设置所有 3 位 - 读取、写入和执行。
        好的,R 不是绝对必须的,但是如果没有它,您就无法列出目录中的条目(因此您必须知道它们的名称)。另一方面,绝对需要执行 - 用户无法读取文件的 inode;所以即使有 W,没有 X 文件也无法创建或修改。 More detailed explanation at this link.

        最后,这些模式在stat 模块中可用,它们的descriptions are in inode(7) man

        示例代码如何检查:

        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
        

        【讨论】:

          【解决方案10】:

          我在通过 argparse 添加参数时遇到了同样的需求。内置的type=FileType('w') 对我不起作用,因为我正在寻找一个目录。我最终编写了自己的方法来解决我的问题。这是 argparse sn-p 的结果。

          #! /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()
          

          结果如下:

          $ 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 ~
          

          我返回并在末尾添加了 print opts.dir,一切似乎都按预期运行。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-01-14
            • 2011-04-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-12-15
            • 2011-04-05
            相关资源
            最近更新 更多