Определение возможности записи в каталог

101

Каким будет лучший способ в Python определить, доступен ли каталог для записи пользователю, выполняющему сценарий? Поскольку это, вероятно, будет связано с использованием модуля os, я должен упомянуть, что запускаю его в среде * nix.

освещенный тигр
источник

Ответы:

185

Хотя то, что предложил Кристоф, является более питоническим решением, в модуле os есть функция os.access для проверки доступа:

os.access('/path/to/folder', os.W_OK) # W_OK - для записи, R_OK - для чтения и т. Д.

Макс Шавабке
источник
4
В зависимости от ситуации, «проще попросить прощения» - не лучший способ даже в Python. Иногда рекомендуется «спросить разрешение», как в случае с упомянутым методом os.access (), например, когда высока вероятность обнаружения ошибки.
mjv 06
53
Тестирования каталога только на бит записи недостаточно, если вы хотите записывать файлы в каталог. Вам также нужно будет проверить бит выполнения, если вы хотите писать в каталог. os.access ('/ path / to / folder', os.W_OK | os.X_OK) С помощью os.W_OK вы можете удалить только каталог (и только если этот каталог пуст)
fthinker
4
Еще одна проблема os.access()- это проверка с использованием реальных UID и GID, а не эффективных . Это могло вызвать странности в средах SUID / SGID. ('но сценарий запускает setuid root, почему он не может записать в файл?')
Alexios
5
Может быть, программа просто хочет знать, не имея необходимости писать. Возможно, он просто захочет изменить внешний вид и / или поведение графического интерфейса пользователя в соответствии со свойством. В этом случае я бы не считал питоническим писать и удалять файл просто в качестве теста.
Bachsau
1
Только что протестировал на сетевом ресурсе Windows. os.access(dirpath, os.W_OK | os.X_OK)возвращает True, даже если у меня нет доступа на запись.
iamanigeeit
69

Это может показаться странным, но общая идиома Python

Проще просить прощения, чем разрешения

Следуя этой идиоме, можно сказать:

Попробуйте написать в соответствующий каталог и перехватите ошибку, если у вас нет на это разрешения.

ChristopheD
источник
6
+1 Python или нет, это действительно самый надежный способ проверки доступа.
John Knoeller
5
Это также устраняет другие ошибки, которые могут произойти при записи на диск - например, не осталось места на диске. В этом сила попытки .. вам не нужно помнить все, что может пойти не так ;-)
Йохен Ритцель
4
Спасибо, парни. Решил использовать os.access, так как скорость - важный фактор в том, что я здесь делаю, хотя я определенно могу понять достоинства того, что «проще просить прощения, чем разрешения». ;)
illuminatedtiger
4
Это отличный IDIO ... м - особенно в сочетании с другой идиомой except: pass- так вы всегда можете быть оптимистичными и высоко ценить себя. / сарказм прочь. Теперь зачем мне, например, пытаться записать что-то в каждый каталог в моей файловой системе, чтобы создать список доступных для записи мест?
Томаш Гандор
4
Может быть, программа просто хочет знать, не имея необходимости писать. Возможно, он просто захочет изменить внешний вид и / или поведение графического интерфейса пользователя в соответствии со свойством. В этом случае я бы не считал питоническим писать и удалять файл просто в качестве теста.
Bachsau
19

Мое решение с использованием 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 я вижу, что действительно существует проблема при использовании там временного файла , см. Issue22107: модуль временного файла неверно интерпретирует ошибку отказа в доступе в 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

В этих случаях, конечно, задержка все еще присутствует.

зак
источник
1
Я думаю, что тот, который использует tempfile, является более чистым, потому что он точно не оставляет остатков.
кузнечик
3
этот метод не работает tempfile. он работает только тогда, когда нет OSErrorсмысла иметь разрешение на запись / удаление. в противном случае этого не произойдет, return Falseпотому что ошибка не будет возвращена, и сценарий не будет продолжать выполнение или завершение работы. ничего не возвращается. он просто застрял на этой линии. однако создание невременного файла, такого как ответ khattam, работает как при разрешении, так и при отказе в разрешении. Помогите?
10

Наткнулся на эту ветку в поисках примеров для кого-то. Первый результат в 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")

Это пытается открыть дескриптор файла для записи и завершается с ошибкой, если указанный файл не может быть записан: это намного легче читать, и это гораздо лучший способ сделать это, чем выполнять предварительную проверку пути к файлу или каталогу. , поскольку это позволяет избежать условий гонки; случаи, когда файл становится недоступным для записи между временем, когда вы запускаете предварительную проверку, и когда вы фактически пытаетесь записать в файл.

Рохак
источник
1
Это относится к файлу, а не к каталогу, о чем спрашивал OP. Вы можете иметь файл в каталоге и сделать так, чтобы каталог не был доступен для записи, но сам файл есть, если файл уже существует. Это может быть важно при системном администрировании, когда вы, например, создаете файлы журналов, которые вы хотите, чтобы уже существовали, но не хотите, чтобы люди использовали каталог журналов для временного пространства.
Mike S
... и на самом деле я проголосовал против, что теперь считаю ошибкой. Как сказал Рохак, существуют проблемы с условиями гонки. Есть и другие проблемы на различных платформах, где вы можете протестировать каталог, и он выглядит доступным для записи, но на самом деле это не так. Выполнение кросс-платформенных проверок доступности для записи в каталогах сложнее, чем кажется. Так что, если вы знаете о проблемах, это может быть прекрасным методом. Я смотрел на это с точки зрения UNIX, что является моей ошибкой. Кто-то отредактирует этот ответ, чтобы я мог удалить свой -1.
Mike S
Я отредактировал его на случай, если вы хотите удалить -1 :) И да, кросс-платформенные проверки каталогов могут стать более сложными, но обычно вы хотите создать / записать файл в этом каталоге - и в этом случае приведенный мной пример все еще применим. Если возникает какая-либо проблема, связанная с разрешениями каталога, она все равно должна выдавать ошибку IOError при попытке открыть дескриптор файла.
Rohaq
Я снял свой голос против. Извините за это и спасибо за ваш вклад.
Mike S
Не беспокойтесь, всегда приветствуются люди, задающие вопросы!
Rohaq
9

Если вас интересует только файл с завивками, os.access(path, os.W_OK)следует делать то, о чем вы просите. Если вместо этого вы хотите узнать, можете ли вы записать в каталог open()тестовый файл для записи (он не должен существовать заранее), поймайте и изучите любой IOError, а затем очистите тестовый файл.

В более общем плане, чтобы избежать атак TOCTOU (проблема только в том случае, если ваш скрипт работает с повышенными привилегиями - suid или cgi или около того), вам не следует на самом деле доверять этим заблаговременным тестам, а отказаться от привилегий, выполнить open()и ожидать файл IOError.

Sverkerw
источник
7

Проверьте биты режима:

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
Это решение только для Unix.
Бьорн Линдквист,
4

Вот что я создал на основе ответа 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")

больше информации о доступе можно найти здесь

Softmixt
источник
2
По сути, это копия ответа Макса Шавабке с небольшой оберткой вокруг него. Делает это быстрой копировальной вставкой, но лучше было бы добавить ее в исходный пост Макса.
Йоррик Слейстер,
1

Я столкнулся с этой же потребностью при добавлении аргумента через argparse. Встроенное type=FileType('w')не работало для меня, поскольку я искал каталог. В итоге я написал свой собственный метод решения моей проблемы. Вот результат с фрагментом 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()

Это приводит к следующему:

$ 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 до конца, и, похоже, все работает как нужно.

796m9XfYTkmp
источник
0

Если вам нужно проверить разрешение другого пользователя (да, я понимаю, что это противоречит вопросу, но может кому-то пригодиться), вы можете сделать это через pwdмодуль и биты режима каталога.

Отказ от ответственности - не работает в Windows, так как не использует модель разрешений POSIX (и pwdмодуль там недоступен), например - решение только для систем * nix.

Обратите внимание, что в каталоге должны быть установлены все 3 бита - чтение, запись и eXecute.
Хорошо, R не является абсолютной необходимостью, но без него вы не можете перечислить записи в каталоге (так что вы должны знать их имена). Execute, с другой стороны, абсолютно необходимо - без него пользователь не может читать inodes файла; поэтому даже имея W, без X файлы не могут быть созданы или изменены. Более подробное объяснение по этой ссылке.

Наконец, режимы доступны в statмодуле, их описание находится в 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
Тодор Минаков
источник