Determinando se um diretório é gravável


101

Qual seria a melhor maneira em Python de determinar se um diretório é gravável para o usuário que está executando o script? Como isso provavelmente envolverá o uso do módulo os, devo mencionar que estou executando-o em um ambiente * nix.

Respostas:


185

Embora o que Christophe sugeriu seja uma solução mais Pythônica, o módulo os tem a função os.access para verificar o acesso:

os.access('/path/to/folder', os.W_OK) # W_OK é para escrita, R_OK para leitura, etc.


4
Dependendo da situação, o "mais fácil de pedir perdão" não é a melhor maneira, mesmo em Python. Às vezes, é aconselhável "pedir permissão" como no método os.access () mencionado, por exemplo, quando a probabilidade de ter que detectar um erro é alta.
mjv

53
Testar um diretório apenas para o bit de gravação não é suficiente se você deseja gravar arquivos no diretório. Você precisará testar o bit de execução também se quiser gravar no diretório. os.access ('/ caminho / para / pasta', os.W_OK | os.X_OK) Com os.W_OK por si só você só pode deletar o diretório (e somente se o diretório estiver vazio)
fthinker

4
Outra pegadinha os.access()é verificar usando o UID e GID reais , não os efetivos . Isso pode causar estranheza em ambientes SUID / SGID. ('mas o script executa setuid root, por que não consegue gravar no arquivo?')
Alexios

5
Talvez um programa apenas queira saber sem ter a necessidade de realmente escrever. Ele pode apenas querer alterar a aparência e / ou comportamento de uma GUI de acordo com a propriedade. Nesse caso, eu não consideraria pythônico escrever e excluir um arquivo apenas como um teste.
Bachsau de

1
Acabei de testar em um compartilhamento de rede do Windows. os.access(dirpath, os.W_OK | os.X_OK)retorna True, mesmo se eu não tiver acesso de gravação.
iamanigeeit

69

Pode parecer estranho sugerir isso, mas um idioma comum do Python é

É mais fácil pedir perdão do que permissão

Seguindo esse idioma, pode-se dizer:

Tente escrever no diretório em questão e detecte o erro se não tiver permissão para fazer isso.


5
+1 Python ou não, esta é realmente a maneira mais confiável de testar o acesso.
John Knoeller

5
Isso também cuida de outros erros que podem acontecer durante a gravação no disco - nenhum espaço em disco sobrando, por exemplo. Esse é o poder de tentar ... você não precisa se lembrar de tudo que pode dar errado ;-)
Jochen Ritzel

4
Obrigado rapazes. Decidi usar os.access porque a velocidade é um fator importante no que estou fazendo aqui, embora possa certamente entender os méritos de "é mais fácil pedir perdão do que permissão". ;)
tigre iluminado de

4
É um ótimo IDIO ... m - especialmente quando combinado com outro idioma except: pass- desta forma você pode sempre ser otimista e ter uma opinião elevada. / sarcasmo desligado. Agora, por que eu iria querer, por exemplo, tentar escrever algo em todos os diretórios do meu sistema de arquivos, para produzir uma lista de locais graváveis?
Tomasz Gandor,

4
Talvez um programa apenas queira saber sem ter a necessidade de realmente escrever. Ele pode apenas querer alterar a aparência e / ou comportamento de uma GUI de acordo com a propriedade. Nesse caso, eu não consideraria pythônico escrever e excluir um arquivo apenas como um teste.
Bachsau de

19

Minha solução usando o tempfilemódulo:

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

Atualização: Depois de testar o código novamente no Windows, vejo que realmente há um problema ao usar o arquivo temporário, consulte o problema 22107: o módulo do arquivo temporário interpreta erroneamente o erro de acesso negado no Windows . No caso de um diretório não gravável, o código trava por vários segundos e, finalmente, lança um IOError: [Errno 17] No usable temporary file name found. Talvez seja isso o que user2171842 estava observando? Infelizmente, o problema não foi resolvido por agora, então, para lidar com isso, o erro também precisa ser detectado:

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

O atraso, é claro, ainda está presente nesses casos.


1
Acho que quem usa tempfile é o mais limpo porque com certeza não deixa resíduos.
gafanhoto

3
este método não está funcionando usando tempfile. ele só funciona quando não há nenhum OSErrorsignificado que ele tenha permissão para gravar / excluir. caso contrário, isso não acontecerá return Falseporque nenhum erro será retornado e o script não continuará sendo executado ou encerrado. nada é devolvido. está apenas preso nessa linha. entretanto, criar um arquivo não temporário, como a resposta de khattam, funciona tanto quando a permissão é permitida ou negada. Socorro?

10

Tropecei neste tópico procurando exemplos para alguém. Primeiro resultado no Google, parabéns!

As pessoas falam sobre a maneira Pythônica de fazer isso neste segmento, mas nenhum exemplo de código simples? Aqui está, para qualquer pessoa que tropeçar em:

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

Isso tenta abrir um identificador de arquivo para gravação e sai com um erro se o arquivo especificado não puder ser gravado: Isso é muito mais fácil de ler e é uma maneira muito melhor de fazer isso do que fazer verificações prévias no caminho do arquivo ou no diretório , pois evita condições de corrida; casos em que o arquivo se torna não gravável entre o momento em que você executa a pré-verificação e quando você realmente tenta gravar no arquivo.


1
Isso se aplica a um arquivo, não a um diretório, que foi solicitado pelo OP. Você pode ter um arquivo em um diretório e fazer com que o diretório não seja gravável, mas o arquivo em si é, caso o arquivo já exista. Isso pode ser importante na administração de sistemas, onde você está, por exemplo, criando arquivos de log que deseja que já existam, mas não quer que as pessoas usem um diretório de log para espaço temporário.
Mike S de

... e na verdade eu votei contra, o que agora acho que foi um erro. Existem problemas, como Rohaq mencionou, com as condições de corrida. Existem outros problemas em várias plataformas onde você pode testar o diretório, e parece gravável, mas na verdade não é. Executar verificações graváveis ​​de diretório de plataforma cruzada é mais difícil do que parece. Portanto, desde que você esteja ciente dos problemas, esta pode ser uma boa técnica. Eu estava olhando para isso de uma perspectiva muito UNIX, o que é meu erro. Alguém edite esta resposta para que eu possa remover meu -1.
Mike S

Eu editei, caso você queira remover o -1 :) E sim, verificações de diretório de plataforma cruzada podem ficar mais complicadas, mas geralmente você está procurando criar / gravar em um arquivo nesse diretório - nesse caso o exemplo que dei ainda deve se aplicar. Se algum problema relacionado à permissão de diretório surgir, ele ainda deve lançar um IOError ao tentar abrir o filehandle.
Rohaq

Eu removi meu voto negativo. Desculpe por isso, e obrigado por sua contribuição.
Mike S

Não se preocupe, pessoas questionando respostas são sempre bem-vindas!
Rohaq de

9

Se você só se preocupa com as permissões do arquivo, os.access(path, os.W_OK)faça o que você pedir. Se, em vez disso, você quiser saber se pode gravar no diretório, open()um arquivo de teste para gravação (ele não deveria existir antes), pegue e examine qualquer um IOErrore limpe o arquivo de teste depois.

De forma mais geral, para evitar ataques TOCTOU (apenas um problema se o seu script for executado com privilégios elevados - suid ou cgi ou algo assim), você não deve realmente confiar nesses testes antecipados, mas descarte privs, faça o open()e espere a IOError.


7

Verifique os bits de modo:

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
Esta solução é apenas para Unix.
Björn Lindqvist

4

Aqui está algo que criei com base na resposta de 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")

mais informações sobre o acesso podem ser encontradas aqui


2
Esta é basicamente uma cópia da resposta de Max Shawabkeh com um pequeno invólucro em volta dela. Torna-o uma pasta de cópia rápida, mas uma ideia melhor seria adicioná-lo à postagem original do Max.
Jorrick Sleijster de

1

Corri para essa mesma necessidade ao adicionar um argumento via argparse. O integrado type=FileType('w')não funcionaria para mim porque eu estava procurando um diretório. Acabei escrevendo meu próprio método para resolver meu problema. Aqui está o resultado com o snippet 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()

Isso resulta no seguinte:

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

Voltei e adicionei print opts.dir ao final, e tudo parece estar funcionando como desejado.


0

Se precisar verificar a permissão de outro usuário (sim, eu percebo que isso contradiz a pergunta, mas pode vir a calhar para alguém), você pode fazer isso através do pwdmódulo e dos bits de modo do diretório.

Aviso - não funciona no Windows, pois não usa o modelo de permissões POSIX (e o pwdmódulo não está disponível lá), por exemplo - solução apenas para sistemas * nix.

Observe que um diretório deve ter todos os 3 bits definidos - Leitura, Gravação e Execução.
Ok, R não é uma necessidade absoluta, mas sem ele você não pode listar as entradas no diretório (então você deve saber seus nomes). A execução, por outro lado, é absolutamente necessária - sem ela, o usuário não pode ler os inodes do arquivo; portanto, mesmo tendo W, sem X os arquivos não podem ser criados ou modificados. Explicação mais detalhada neste link.

Por fim, os modos estão disponíveis no statmódulo, suas descrições estão no inode (7) man .

Código de amostra como verificar:

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.