Określanie, czy katalog jest zapisywalny

101

Jaki byłby najlepszy sposób w Pythonie na określenie, czy katalog jest zapisywalny dla użytkownika wykonującego skrypt? Ponieważ prawdopodobnie będzie to wymagało użycia modułu os, powinienem wspomnieć, że uruchamiam go w środowisku * nix.

podświetlany tygrys
źródło

Odpowiedzi:

185

Chociaż to, co zasugerował Christophe, jest rozwiązaniem bardziej Pythonowym, moduł os ma funkcję os.access do sprawdzania dostępu:

os.access('/path/to/folder', os.W_OK) # W_OK służy do pisania, R_OK do czytania itp.

Max Shawabkeh
źródło
4
W zależności od sytuacji, „łatwiej prosić o przebaczenie” nie jest najlepszym sposobem, nawet w Pythonie. Czasami zaleca się „zapytać o pozwolenie”, tak jak w przypadku wspomnianej metody os.access (), na przykład gdy prawdopodobieństwo wykrycia błędu jest wysokie.
mjv
53
Testowanie katalogu tylko dla bitu zapisu nie wystarczy, jeśli chcesz zapisywać pliki w katalogu. Będziesz musiał również przetestować bit wykonania, jeśli chcesz pisać do katalogu. os.access ('/ path / to / folder', os.W_OK | os.X_OK) Za pomocą samego os.W_OK możesz tylko usunąć katalog (i tylko jeśli ten katalog jest pusty)
fthinker
4
Innym problemem os.access()jest to, że sprawdza przy użyciu prawdziwego UID i GID, a nie skutecznych . Może to powodować dziwności w środowiskach SUID / SGID. („ale skrypt uruchamia setuid root, dlaczego nie może zapisywać do pliku?”)
Alexios
5
Może program po prostu chce wiedzieć bez konieczności pisania. Może po prostu chcieć zmienić wygląd i / lub zachowanie GUI zgodnie z właściwością. W takim przypadku nie uważałbym, że pisanie i usuwanie pliku tylko jako test jest pythonowe.
Bachsau
1
Właśnie przetestowano na udziale sieciowym Windows. os.access(dirpath, os.W_OK | os.X_OK)zwraca True, nawet jeśli nie mam dostępu do zapisu.
iamanigeeit
69

Sugerowanie tego może wydawać się dziwne, ale powszechnym idiomem w Pythonie jest

Łatwiej jest prosić o przebaczenie niż o pozwolenie

Idąc za tym idiomem, można by powiedzieć:

Spróbuj napisać do odpowiedniego katalogu i wyłap błąd, jeśli nie masz do tego uprawnień.

ChristopheD
źródło
5
+1 Python czy nie, to naprawdę najbardziej niezawodny sposób na sprawdzenie dostępu.
John Knoeller
5
Eliminuje to również inne błędy, które mogą się zdarzyć podczas zapisywania na dysku - na przykład brak miejsca na dysku. To jest siła próbowania… nie musisz pamiętać wszystkiego, co może pójść nie tak ;-)
Jochen Ritzel,
4
Dzięki chłopaki. Zdecydowałem się na os.access, ponieważ prędkość jest ważnym czynnikiem w tym, co tutaj robię, chociaż z pewnością rozumiem zalety w stwierdzeniu, że „łatwiej jest prosić o przebaczenie niż o pozwolenie”. ;)
illuminatedtiger
4
To świetne IDIO ... m - zwłaszcza w połączeniu z innym idiomem except: pass- w ten sposób zawsze możesz być optymistą i dobrze o sobie myśleć. / sarkazm wyłączony. Dlaczego miałbym chcieć, na przykład, spróbować zapisać coś w każdym katalogu w moim systemie plików, aby utworzyć listę zapisywalnych lokalizacji?
Tomasz Gandor
4
Może program po prostu chce wiedzieć bez konieczności pisania. Może po prostu chcieć zmienić wygląd i / lub zachowanie GUI zgodnie z właściwością. W takim przypadku nie uważałbym, że pisanie i usuwanie pliku tylko jako test jest pythonowe.
Bachsau
19

Moje rozwiązanie z wykorzystaniem tempfilemodułu:

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

Aktualizacja: po ponownym przetestowaniu kodu w systemie Windows widzę, że rzeczywiście występuje problem podczas używania tam pliku tymczasowego, zobacz problem22107: moduł tymczasowego pliku błędnie interpretuje błąd odmowy dostępu w systemie Windows . W przypadku katalogu, którego nie można zapisać, kod zawiesza się na kilka sekund, a na koniec generuje plik IOError: [Errno 17] No usable temporary file name found. Może to właśnie obserwował użytkownik2171842? Niestety problem nie został na razie rozwiązany, więc aby sobie z tym poradzić, należy również wyłapać błąd:

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

Oczywiście w takich przypadkach opóźnienie nadal występuje.

zak
źródło
1
Myślę, że ten, który używa pliku tymczasowego, jest czyszczący, ponieważ na pewno nie pozostawia pozostałości.
konik polny
3
ta metoda nie działa przy użyciu tempfile. działa tylko wtedy, gdy nie ma OSErrorznaczenia, że ​​ma uprawnienia do zapisu / usuwania. w przeciwnym razie tak się nie stanie, return Falseponieważ nie zostanie zwrócony żaden błąd, a skrypt nie będzie kontynuował wykonywania ani kończy pracy. nic nie jest zwracane. po prostu utknął na tej linii. Jednak utworzenie pliku nie tymczasowego, takiego jak odpowiedź khattam, działa zarówno wtedy, gdy zezwolenie jest dozwolone, jak i nie. Wsparcie?
10

Natknąłem się na ten wątek, szukając przykładów dla kogoś. Pierwszy wynik w Google, gratulacje!

W tym wątku ludzie mówią o sposobie robienia tego w Pythonie, ale nie ma prostych przykładów kodu? Proszę bardzo, dla każdego, kto się potknie:

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

To próbuje otworzyć uchwyt pliku do zapisu i kończy pracę z błędem, jeśli określony plik nie może zostać zapisany: Jest to o wiele łatwiejsze do odczytania i jest o wiele lepszym sposobem zrobienia tego niż wykonywanie wstępnych kontroli ścieżki pliku lub katalogu ponieważ unika warunków wyścigu; przypadki, w których plik staje się niemożliwy do zapisu między momentem uruchomienia kontroli wstępnej a faktyczną próbą zapisu do pliku.

Rohaq
źródło
1
Dotyczy to pliku, a nie katalogu, o co prosił OP. Możesz mieć plik w katalogu i nie można w nim zapisywać, ale sam plik jest, jeśli plik już istnieje. Może to być ważne w administrowaniu systemami, gdzie np. Tworzysz pliki dziennika, które chcesz, aby już istniały, ale nie chcesz, aby ludzie używali katalogu dziennika dla przestrzeni tymczasowej.
Mike S
... i właściwie przegłosowałem to, co teraz uważam za błąd. Jak wspomniał Rohaq, są problemy z warunkami wyścigu. Istnieją inne problemy na różnych platformach, na których można przetestować katalog i wygląda na to, że można go zapisać, ale w rzeczywistości tak nie jest. Wykonywanie międzyplatformowych sprawdzeń katalogów z możliwością zapisu jest trudniejsze niż się wydaje. Tak długo, jak jesteś świadomy problemów, może to być dobra technika. Patrzyłem na to ze zbyt uniksowej perspektywy, co jest moim błędem. Ktoś edytuje tę odpowiedź, abym mógł usunąć -1.
Mike S
Edytowałem to, na wypadek, gdybyś chciał usunąć -1 :) I tak, sprawdzanie katalogu na różnych platformach może być bardziej skomplikowane, ale generalnie chcesz utworzyć / zapisać plik w tym katalogu - w takim przypadku podany przeze mnie przykład powinien nadal obowiązywać. Jeśli pojawi się jakiś problem związany z uprawnieniami do katalogu, nadal powinien generować błąd IOError podczas próby otwarcia uchwytu pliku.
Rohaq
Usunąłem swój głos przeciw. Przepraszamy za to i dziękuję za Twój wkład.
Mike S
Nie martw się, ludzie pytający o odpowiedzi są zawsze mile widziani!
Rohaq,
9

Jeśli zależy Ci tylko na uprawnieniach do pliku, os.access(path, os.W_OK)powinieneś zrobić to, o co prosisz. Jeśli natomiast chcemy wiedzieć, czy można zapisywać do katalogu open()plik testowy na piśmie (nie powinien istnieć wcześniej), złapać i zbadać każdy IOError, i oczyścić plik testowy później.

Mówiąc bardziej ogólnie, aby uniknąć ataków TOCTOU (tylko problem, jeśli twój skrypt działa z podwyższonymi uprawnieniami - suid lub cgi lub coś podobnego), nie powinieneś naprawdę ufać tym testom z wyprzedzeniem, ale porzuć uprawnienia, zrób open()i oczekuj the IOError.

sverkerw
źródło
7

Sprawdź bity trybu:

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)
     )
Joe Koberg
źródło
4
To rozwiązanie dotyczy tylko systemu Unix.
Björn Lindqvist
4

Oto coś, co stworzyłem na podstawie odpowiedzi ChristopheDa:

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"
Khattam
źródło
3
 if os.access(path_to_folder, os.W_OK) is not True:
            print("Folder not writable")
 else :
            print("Folder writable")

więcej informacji o dostępie można znaleźć tutaj

Softmixt
źródło
2
Jest to w zasadzie kopia odpowiedzi Maxa Shawabkeha z niewielkim opakowaniem. Sprawia, że ​​jest to szybkie wklejanie, ale lepszym pomysłem byłoby dodanie go do oryginalnego postu Maxa.
Jorrick Sleijster
1

Napotkałem tę samą potrzebę, dodając argument za pośrednictwem argparse. Wbudowany type=FileType('w')nie działałby dla mnie, ponieważ szukałem katalogu. Skończyło się na napisaniu własnej metody rozwiązania mojego problemu. Oto wynik z argparse snippet.

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

Z tego wynika:

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

Wróciłem i na końcu dodałem print opts.dir i wszystko wydaje się działać zgodnie z oczekiwaniami.

796m9XfYTkmp
źródło
0

Jeśli chcesz sprawdzić uprawnienia innego użytkownika (tak, zdaję sobie sprawę, że przeczy to pytaniu, ale może się komuś przydać), możesz to zrobić przez pwdmoduł i bity trybu katalogu.

Disclaimer - nie działa na Windows, ponieważ nie korzysta z modelu uprawnień POSIX (a pwdmoduł nie jest tam dostępny), np. - rozwiązanie tylko dla systemów * nix.

Zauważ, że katalog musi mieć ustawione wszystkie 3 bity - odczyt, zapis i eXecute.
Ok, R nie jest absolutną koniecznością, ale bez niego nie możesz wyświetlić listy wpisów w katalogu (więc musisz znać ich nazwy). Z drugiej strony wykonanie jest absolutnie potrzebne - bez niego użytkownik nie może odczytać i-węzłów pliku; więc nawet mając W, bez plików X nie można tworzyć ani modyfikować. Bardziej szczegółowe wyjaśnienie pod tym linkiem.

Wreszcie tryby są dostępne w statmodule, ich opisy znajdują się w inode (7) man .

Przykładowy kod jak sprawdzić:

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
Todor Minakov
źródło