Determinar si un directorio se puede escribir

101

¿Cuál sería la mejor manera en Python de determinar si un directorio es escribible para el usuario que ejecuta el script? Dado que esto probablemente involucrará el uso del módulo del sistema operativo, debo mencionar que lo estoy ejecutando en un entorno * nix.

tigre iluminado
fuente

Respuestas:

185

Aunque lo que sugirió Christophe es una solución más Pythonic, el módulo os tiene la función os.access para verificar el acceso:

os.access('/path/to/folder', os.W_OK) # W_OK es para escribir, R_OK para leer, etc.

Max Shawabkeh
fuente
4
Dependiendo de la situación, el "más fácil de pedir perdón" no es la mejor manera, incluso en Python. A veces es recomendable "pedir permiso" como con el método os.access () mencionado, por ejemplo cuando la probabilidad de tener que detectar un error es alta.
mjv
53
Probar un directorio solo para el bit de escritura no es suficiente si desea escribir archivos en el directorio. Deberá probar también el bit de ejecución si desea escribir en el directorio. os.access ('/ ruta / a / carpeta', os.W_OK | os.X_OK) Con os.W_OK por sí solo, solo puede eliminar el directorio (y solo si ese directorio está vacío)
fthinker
4
Otro problema os.access()es que verifica utilizando el UID y GID reales , no los efectivos . Esto podría causar rarezas en entornos SUID / SGID. ('pero el script ejecuta setuid root, ¿por qué no puede escribir en el archivo?')
Alexios
5
Quizás un programa solo quiera saber sin tener la necesidad de escribir. Es posible que solo desee cambiar el aspecto y / o el comportamiento de una GUI de acuerdo con la propiedad. En ese caso, no consideraría pitónico escribir y eliminar un archivo solo como una prueba.
Bachsau
1
Recientemente probado en un recurso compartido de red de Windows. os.access(dirpath, os.W_OK | os.X_OK)devuelve True incluso si no tengo acceso de escritura.
iamanigeeit
69

Puede parecer extraño sugerir esto, pero un modismo común de Python es

Es más fácil pedir perdón que pedir permiso

Siguiendo ese modismo, se podría decir:

Intente escribir en el directorio en cuestión y detecte el error si no tiene permiso para hacerlo.

Cristóbal
fuente
5
+1 Python o no, esta es realmente la forma más confiable de probar el acceso.
John Knoeller
5
Esto también se encarga de otros errores que pueden ocurrir al escribir en el disco, por ejemplo, no queda espacio en el disco. Ese es el poder de intentarlo ... no necesitas recordar todo lo que puede salir mal ;-)
Jochen Ritzel
4
Gracias chicos. Decidí ir con os.access ya que la velocidad es un factor importante en lo que estoy haciendo aquí, aunque ciertamente puedo entender los méritos de "es más fácil pedir perdón que permiso". ;)
illuminatedtiger
4
Es un gran IDIO ... m, especialmente cuando se combina con otro modismo except: pass, de esta manera siempre puedes ser optimista y pensar muy bien en ti mismo. / sarcasmo fuera. Ahora, ¿por qué querría, por ejemplo, intentar escribir algo en cada directorio de mi sistema de archivos, para producir una lista de ubicaciones grabables?
Tomasz Gandor
4
Quizás un programa solo quiera saber sin tener la necesidad de escribir. Es posible que solo desee cambiar el aspecto y / o el comportamiento de una GUI de acuerdo con la propiedad. En ese caso, no consideraría pitónico escribir y eliminar un archivo solo como una prueba.
Bachsau
19

Mi solución usando el 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

Actualización: después de probar el código nuevamente en Windows, veo que, de hecho, hay un problema al usar el archivo temporal allí, consulte el problema 22107: el módulo del archivo temporal malinterpreta el error de acceso denegado en Windows . En el caso de un directorio que no se puede escribir, el código se cuelga durante varios segundos y finalmente arroja un IOError: [Errno 17] No usable temporary file name found. ¿Quizás esto es lo que estaba observando user2171842? Desafortunadamente, el problema no está resuelto por ahora, por lo que para manejarlo, el error también debe detectarse:

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

El retraso, por supuesto, todavía está presente en estos casos.

zak
fuente
1
Creo que el que usa el archivo temporal es el más limpio porque seguro que no deja residuos.
saltamontes
3
este método no funciona con tempfile. solo funciona cuando no hay ningún OSErrorsignificado que tenga permiso para escribir / borrar. de lo contrario, no lo hará return Falseporque no se devuelve ningún error y el script no continuará ejecutándose o saliendo. no se devuelve nada. simplemente está atascado en esa línea. sin embargo, la creación de un archivo no temporal como la respuesta de khattam funciona tanto cuando se permite como se deniega el permiso. ¿ayuda?
10

Tropecé con este hilo buscando ejemplos para alguien. Primer resultado en Google, ¡felicidades!

La gente habla sobre la forma Pythonic de hacerlo en este hilo, pero ¿no hay ejemplos de código simples? Aquí tiene, para cualquier otra persona que se tropiece:

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

Esto intenta abrir un identificador de archivo para escritura y sale con un error si no se puede escribir en el archivo especificado: Esto es mucho más fácil de leer y es una forma mucho mejor de hacerlo en lugar de realizar comprobaciones previas en la ruta del archivo o el directorio. , ya que evita las condiciones de carrera; casos en los que el archivo no se puede escribir entre el momento en que ejecuta la comprobación previa y cuando realmente intenta escribir en el archivo.

Rohaq
fuente
1
Esto se aplica a un archivo, no a un directorio, que es lo que pidió el OP. Puede tener un archivo en un directorio y no se puede escribir en el directorio, pero el archivo en sí sí, si el archivo ya existe. Esto puede ser importante en la administración de sistemas donde, por ejemplo, está creando archivos de registro que desea que ya existan pero no desea que la gente use un directorio de registro para el espacio temporal.
Mike S
... y de hecho lo voté en contra, lo que ahora creo que fue un error. Hay problemas, como mencionó Rohaq, con las condiciones de carrera. Hay otros problemas en varias plataformas en las que puede probar el directorio y parece que se puede escribir, pero en realidad no lo es. Realizar comprobaciones de escritura de directorios multiplataforma es más difícil de lo que parece. Entonces, siempre que esté al tanto de los problemas, esta puede ser una buena técnica. Lo estaba viendo desde una perspectiva demasiado UNIX-y, que es mi error. Alguien edite esta respuesta para que pueda eliminar mi -1.
Mike S
Lo he editado, en caso de que desee eliminar el -1 :) Y sí, las comprobaciones de directorio multiplataforma pueden volverse más complicadas, pero generalmente está buscando crear / escribir en un archivo en ese directorio, en cuyo caso el ejemplo que he dado todavía debería aplicarse. Si surge algún problema relacionado con los permisos de directorio, aún debería generar un IOError al intentar abrir el identificador de archivo.
Rohaq
Eliminé mi voto negativo. Lo siento y gracias por tu contribución.
Mike S
No se preocupe, las personas que preguntan respuestas siempre son bienvenidas.
Rohaq
9

Si solo te preocupan los permisos de archivo, os.access(path, os.W_OK)debes hacer lo que pidas. Si, en cambio, desea saber si puede escribir en el directorio, open()un archivo de prueba para escribir (no debería existir de antemano), capturar y examinar alguno IOError, y limpiar el archivo de prueba después.

De manera más general, para evitar ataques TOCTOU (solo un problema si su script se ejecuta con privilegios elevados, suid o cgi o algo así), no debe confiar realmente en estas pruebas anticipadas, pero elimine los privilegios, haga open()y espere el IOError.

Sverkerw
fuente
7

Compruebe los 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)
     )
Joe Koberg
fuente
4
Esta solución es solo para Unix.
Björn Lindqvist
4

Aquí hay algo que creé basado en la respuesta 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"
khattam
fuente
3
 if os.access(path_to_folder, os.W_OK) is not True:
            print("Folder not writable")
 else :
            print("Folder writable")

más información sobre el acceso se puede encontrar aquí

Softmixt
fuente
2
Esta es básicamente una copia de la respuesta de Max Shawabkeh con un pequeño envoltorio alrededor. Lo convierte en una copia rápida y pegado, pero una mejor idea sería haberlo agregado a la publicación original de Max.
Jorrick Sleijster
1

Me encontré con esta misma necesidad al agregar un argumento a través de argparse. El integrado type=FileType('w')no funcionaría para mí ya que estaba buscando un directorio. Terminé escribiendo mi propio método para resolver mi problema. Aquí está el resultado con el fragmento 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()

Eso resulta en lo siguiente:

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

Regresé y agregué print opts.dir hasta el final, y todo parece estar funcionando como lo deseaba.

796m9XfYTkmp
fuente
0

Si necesita verificar el permiso de otro usuario (sí, me doy cuenta de que esto contradice la pregunta, pero puede ser útil para alguien), puede hacerlo a través del pwdmódulo y los bits de modo del directorio.

Descargo de responsabilidad : no funciona en Windows, ya que no usa el modelo de permisos POSIX (y el pwdmódulo no está disponible allí), por ejemplo: solución solo para sistemas * nix.

Tenga en cuenta que un directorio debe tener todos los 3 bits establecidos: lectura, escritura y eXecute.
Ok, R no es una necesidad absoluta, pero sin ella no puede enumerar las entradas en el directorio (por lo que debe saber sus nombres). Por otro lado, ejecutar es absolutamente necesario, sin que el usuario no pueda leer los inodos del archivo; por lo que incluso teniendo W, sin X, los archivos no se pueden crear ni modificar. Explicación más detallada en este enlace.

Finalmente, los modos están disponibles en el statmódulo, sus descripciones están en inode (7) man .

Código de muestra cómo 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
Todor Minakov
fuente