Визначення того, чи можна записувати каталог


101

Що було б найкращим способом у Python визначити, чи є каталог, що записується для користувача, що виконує сценарій? Оскільки це, ймовірно, передбачає використання модуля os, я повинен зазначити, що я запускаю його в середовищі * nix.

Відповіді:


185

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

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


4
Залежно від ситуації, "легше попросити пробачення" - це не найкращий спосіб навіть у Python. Іноді доцільно "запитувати дозвіл", як у згаданому методі os.access (), наприклад, коли ймовірність виникнення помилки висока.
mvv

53
Тестування каталогу лише для біта запису недостатньо, якщо ви хочете записати файли в каталог. Вам також потрібно буде протестувати біт виконання, якщо ви хочете записати його в каталог. os.access ('/ path / to / folder', os.W_OK | os.X_OK) За допомогою os.W_OK ви можете видалити лише каталог (і лише якщо цей каталог порожній)
fthinker

4
Інша проблема os.access()- це перевірка за допомогою реальних UID та GID, а не ефективних . Це може спричинити дивацтво в середовищі SUID / SGID. ("але сценарій запускає встановлений корінь, чому він не може записати у файл?")
Alexios

5
Можливо, програма просто хоче знати, не маючи потреби насправді писати. Це може просто змінити зовнішній вигляд та / або поведінку графічного інтерфейсу відповідно до властивості. У такому випадку я б не вважав пітонічним писати та видаляти файл лише як тест.
Бахсау

1
Щойно перевірена на спільній мережі Windows. os.access(dirpath, os.W_OK | os.X_OK)повертає True, навіть якщо у мене немає доступу для запису
iamanigeeit

69

Це може здатися дивним, але загальна ідіома Python - це

Простіше просити пробачення, ніж дозволу

Слідуючи цій ідіомі, можна сказати:

Спробуйте записатись у відповідний каталог і введіть помилку, якщо у вас немає дозволу на це.


5
+1 Python чи ні, це дійсно найнадійніший спосіб перевірити доступ.
Джон Кноеллер

5
Це також враховує інші помилки, які можуть трапитися під час запису на диск - наприклад, не залишається дискового простору. Це сила спроб .. вам не потрібно пам’ятати про все, що може піти не так ;-)
Jochen Ritzel

4
Спасибі, хлопці. Вирішив поїхати з os.access, оскільки швидкість є важливим фактором у тому, що я тут роблю, хоча я, безумовно, можу зрозуміти достоїнства в тому, що "простіше просити пробачення, ніж дозволу". ;)
освітленийкоже

4
Це чудовий ІДІО ... м - особливо в поєднанні з іншою ідіомою except: pass- таким чином ви завжди можете бути оптимістичними та думати над собою. / сарказм вимкнено. Тепер чому я хотів би, наприклад, спробувати написати щось у кожен каталог моєї файлової системи, створити список записуваних місць?
Томаш Гандор

4
Можливо, програма просто хоче знати, не маючи потреби насправді писати. Це може просто змінити зовнішній вигляд та / або поведінку графічного інтерфейсу відповідно до властивості. У такому випадку я б не вважав пітонічним писати та видаляти файл лише як тест.
Бахсау

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 я бачу, що справді існує проблема при використанні tempfile , див. Issue22107: модуль tempfile неправильно тлумачить помилку в доступі, заборонений у Windows . У випадку, коли не записується каталог, код зависає кілька секунд і, нарешті, кидає an IOError: [Errno 17] No usable temporary file name found. Можливо, це спостерігав користувач2171842? На жаль, проблема наразі не вирішена, щоб вирішити цю проблему, помилка також повинна бути зрозуміла:

    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, привіт!

Люди говорять про пітонічний спосіб зробити це в цій темі, але не прості приклади коду? Ось для всіх, хто натрапляє на:

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
Це стосується файлу, а не каталогу, який запитував ОП. Ви можете мати файл у каталозі, а каталог не може бути записаний, але сам файл є, якщо файл вже існує. Це може бути важливо в системному адмініструванні, де ви, наприклад, створюєте файли журналів, які ви хочете вже існувати, але не бажаєте, щоб люди використовували каталог журналів для тимчасового простору.
Майк S

... і насправді я проголосував за це, що зараз я вважаю помилкою. Як згадував Рохак, існують проблеми з умовами перегонів. Існують інші проблеми на різних платформах, де ви можете протестувати каталог, і він виглядає записаним, але насправді це не так. Здійснювати перевірку для запису міжплатформних каталогів складніше, ніж це виглядає. Тож поки ви обізнані з проблемами, це може бути чудовою технікою. Я дивився на це з надто UNIX-y точки зору, що є моєю помилкою. Хтось редагує цю відповідь, щоб я міг видалити -1.
Майк S

Я відредагував це, якщо ви хочете видалити -1 :) І так, перевірки міжплатформних каталогів можуть ускладнитися, але, як правило, ви хочете створити / записати файл у цей каталог - у такому випадку приклад, який я наводив, все ж повинен застосовуватися. Якщо з’являється деяка проблема, пов’язана з дозволом каталогів, вона все-таки повинна викинути IOError при спробі відкрити файл файлу.
Rohaq

Я вилучив свій потік. Вибачте за це, і дякую за ваш внесок.
Майк S

Не хвилюйтесь, люди, які ставлять під сумнів відповіді, завжди вітаються!
Rohaq

9

Якщо ви дбаєте лише про файлові тарілки, os.access(path, os.W_OK)виконайте те, що вам потрібно. Якщо ви замість цього хочете дізнатися, чи можете ви писати в каталог, open()тестовий файл для написання (він не повинен існувати раніше), виберіть і вивчіть будь-який IOError, а потім очистіть тестовий файл.

Загалом, щоб уникнути атак TOCTOU (лише проблема, якщо ваш скрипт працює з підвищеними привілеями - suid або cgi тощо), вам не слід довіряти цим тестам заздалегідь, але кидайте приватні особи, робіть open()і очікуйте то IOError.


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

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


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 до кінця, і все, здається, функціонує як потрібно.


0

Якщо вам потрібно перевірити дозвіл іншого користувача (так, я розумію, що це суперечить питанню, але може бути корисним для когось), ви можете зробити це через pwdмодуль, і біт режиму каталогу.

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

Зверніть увагу, що в каталозі повинні бути встановлені всі 3 біти - Read, Write та eXecute.
Гаразд, R не є абсолютним обов'язком, але без нього ви не можете перелічити записи в каталозі (тому ви повинні знати їх назви). Виконання з іншого боку абсолютно необхідне - без цього користувач не може прочитати вставки файлу; тож навіть маючи W, без X-файлів неможливо створити чи змінити. Більш детальне пояснення за цим посиланням.

Нарешті, statмодулі доступні в модулі, їх описи знаходяться в людині inode (7) .

Приклад коду, як перевірити:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.