Vérifier si un chemin est valide en Python sans créer de fichier à la cible du chemin

j'ai un chemin (y compris le nom du répertoire et du fichier).

Je dois tester si le nom de fichier est valide, par exemple si le système de fichiers me permettra de créer un fichier avec un tel nom.

Le nom de fichier comporte des caractères unicode .

il est sûr de supposer que le segment de répertoire du chemin est valide et accessible ( j'essayais de rendre la question plus applicable, et apparemment je suis allé trop loin ).

Je ne veux pas avoir à échapper à quoi que ce soit à moins que je ont à.

je posterais bien certains des exemples de caractères avec lesquels je traite, mais apparemment ils sont automatiquement supprimés par le système stack-exchange. Quoi qu'il en soit , je veux garder les entités unicode standard comme ö , et seulement échapper les choses qui sont invalides dans un nom de fichier.


Voici la prise. il peut (ou ne pas) y avoir déjà un fichier à la cible du chemin. je dois garder ce fichier s'il existe, et ne pas créer un fichier s'il n'existe pas.

fondamentalement, je veux vérifier si je pourrait écrire à un chemin sans réellement ouvrir le chemin pour l'écriture (et la création automatique de fichier/sabotage qui implique typiquement).

As tel:

try:
    open(filename, 'w')
except OSError:
    # handle error here

d'ici

N'est pas acceptable, car il va écraser le fichier existant, que je ne veux pas toucher (s'il est là), ou créer ledit fichier s'il ne l'est pas.

je sais que je peux le faire:

if not os.access(filePath, os.W_OK):
    try:
        open(filePath, 'w').close()
        os.unlink(filePath)
    except OSError:
        # handle error here

mais cela va créer le fichier au filePath , que je devrais alors os.unlink .

In à la fin, il semble qu'il dépense 6 ou 7 lignes pour faire quelque chose qui devrait être aussi simple que os.isvalidpath(filePath) ou similaire.


de côté, j'en ai besoin pour fonctionner sur (au moins) les fenêtres et les MacOS, donc j'aimerais éviter les trucs spécifiques à la plate-forme.

"

53
demandé sur Community 2012-03-02 15:26:38

5 réponses

tl; dr

appeler la fonction is_path_exists_or_creatable() définie ci-dessous.

Strictement Python 3. C'est juste notre façon de faire.

Un Conte de Deux Questions:

à La question "Comment puis-je tester le chemin de validité et valable chemins d'accès, l'existence ou writability de ces chemins?"est clairement deux questions distinctes. Les deux sont intéressants, et n'ont ni reçu une véritable réponse satisfaisante ici... ou bien, n'importe où que je pourrais grep.

vikki 's réponse probablement hews la plus proche, mais a la remarquable inconvénients de l':

  • ouverture inutile ( ...et puis ne pas fermer de manière fiable ) les poignées de fichier.
  • Inutilement de l'écriture ( ...et pas fiable fermer ou supprimer ) 0 octets des fichiers.
  • en Ignorant les OS des erreurs spécifiques de la différenciation entre non-ignorable invalide chemins d'accès et des ignorable problèmes de système de fichiers. Sans surprise, C'est critique sous Windows. ( voir ci-dessous. )
  • en Ignorant les conditions de concurrence résultant de processus externes simultanément (re)passer parent répertoires du chemin d'accès à tester. ( voir ci-dessous. )
  • Ignorer les délais de connexion résultant de ce chemin d'accès résidant sur des systèmes de fichiers périmés, lents ou temporairement inaccessibles. Ce pourrait exposer les services faisant face au public à des attaques potentielles DoS . ( voir ci-dessous. )

on va arranger tout ça.

Question #0: Qu'est-ce que la validité de nom de chemin de nouveau?

avant de lancer notre les combinaisons de viande fragile dans le python-riddled moshpits de la douleur, nous devrions probablement définir ce que nous entendons par "validité pathname."Ce qui définit la validité, exactement?

par" validité du chemin d'accès", nous entendons la exactitude syntaxique d'un chemin d'accès par rapport au système de fichiers racine du système actuel – indépendamment du fait que ce chemin ou les répertoires parents de celui-ci existent physiquement. Un chemin est syntaxiquement correct sous cette définition si elle est conforme à toutes les exigences syntaxiques du système de fichiers racine.

Par "système de fichiers racine," nous entendons:

  • sur les systèmes compatibles POSIX, le système de fichiers monté sur le répertoire racine ( / ).
  • sur Windows, le système de fichiers monté sur %HOMEDRIVE% , la lettre de lecteur suffixée contenant L'installation actuelle de Windows (typiquement mais pas nécessairement C: ).

la signification de "exactitude syntaxique", à son tour, dépend du type de système de fichiers racine. Pour ext4 (et la plupart mais pas tous les systèmes de fichiers compatibles POSIX), un chemin est syntaxiquement correct si et seulement si ce chemin:

  • ne contient aucun octet nul (i.e., \x00 en Python). il s'agit d'une exigence stricte pour tous les systèmes de fichiers compatibles avec POSIX.
  • ne contient aucun composant de chemin de plus de 255 octets (par exemple 'a'*256 en Python). Un composant de chemin est un substrat le plus long d'un chemin ne contenant pas de caractère / (par ex., bergtatt , ind , i , et fjeldkamrene dans le chemin d'accès /bergtatt/ind/i/fjeldkamrene ).

Syntaxique correct. Système de fichiers racine. C'est tout.

Question #1: Comment Devons-Nous Maintenant Faire La Validité Du Chemin?

valider les noms de chemins en Python est étonnamment non intuitif. Je suis en accord avec faux nom ici: le paquet officiel os.path devrait fournir une solution hors-de-la-boîte pour cela. Pour des raisons inconnues (et probablement inconfortables), ce n'est pas le cas. Heureusement, déroulez votre propre solution ad-hoc n'est pas que déchirant intestin...

O. K., ça l'est en fait. C'est Poilu; c'est méchant; il doit gronder quand il rit et ricane quand il brille. Mais ce que tu vas faire? Nuthin'.

nous allons bientôt descendre dans l'abîme radioactif du code de bas niveau. Mais d'abord, parlons boutique de haut niveau. Les fonctions standard os.stat() et os.lstat() soulèvent les exceptions suivantes lorsqu'elles sont passées des noms de chemin invalides:

  • pour les noms de chemin résidant dans des annuaires non existants, exemples de FileNotFoundError .
  • pour les noms de chemins résidant dans des répertoires existants:
    • sous Windows, instances de WindowsError dont l'attribut winerror est 123 (i.e., ERROR_INVALID_NAME ).
    • sous toutes les autres OS:
    • pour les noms de chemins contenant des octets nuls (i.e., '\x00' ), les instances de TypeError .
    • pour les noms de chemin contenant des composants de chemin plus longs que 255 octets, instances de OSError dont l'attribut errcode est:
      • Sous SunOS et les *BSD de la famille des Systèmes d'exploitation, errno.ERANGE . (Il s'agit apparemment d'un bogue de niveau OS, autrement appelé "interprétation sélective" de la norme POSIX.)
      • sous toutes les autres ose, errno.ENAMETOOLONG .

ce qui implique que seulement les noms de chemins la résidence dans les répertoires existants est validable. les fonctions os.stat() et os.lstat() soulèvent des exceptions génériques FileNotFoundError lorsque des noms de chemin passés résident dans des répertoires non existants, que ces noms de chemin soient invalides ou non. L'existence d'un répertoire a préséance sur l'invalidité du chemin d'accès.

est-ce que cela signifie que les noms de chemin qui résident dans des répertoires non existants sont Non validable? Oui, à moins que nous modifiez ces noms de chemin pour qu'ils résident dans les répertoires existants. C'est que même en toute sécurité faisable, cependant? La modification d'un chemin ne devrait-elle pas nous empêcher de valider le chemin original?

pour répondre à cette question, rappelons d'en haut que les noms de chemin syntaxiquement corrects sur le système de fichiers ext4 ne contiennent pas de composants de chemin (a) contenant des octets nuls ou (b) sur 255 octets de longueur. Par conséquent, un chemin ext4 est valide si et seulement si tous les composants de chemin dans ce chemin d'accès sont valides. C'est le cas de most 15191850920 "real-world filesystems d'intérêt.

est-ce que cette perspicacité pédante nous aide vraiment? Oui. Il réduit le plus grand problème de validation du chemin d'accès complet en un seul clic au plus petit problème de validation de tous les composants du chemin d'accès dans ce chemin d'accès. Tout chemin arbitraire est valide (que ce soit pathname réside ou non dans un répertoire existant) d'une manière multiplateforme en suivant l'algorithme suivant:

  1. Diviser chemin dans chemin d'accès des composants (par ex., le nom de /troldskog/faren/vild dans la liste ['', 'troldskog', 'faren', 'vild'] ).
  2. pour chacun de ces composants:
    1. Rejoindre le chemin d'accès d'un répertoire garanti avec ce composant dans un nouveau chemin d'accès temporaire (par exemple, /troldskog ) .
    2. passez ce chemin à os.stat() ou os.lstat() . Si ce chemin et donc ce composant sont invalides, cet appel est garanti pour soulever une exception exposant le type d'invalidité plutôt qu'une exception Générique FileNotFoundError . Pourquoi? parce que ce chemin réside dans un répertoire existant. (la logique circulaire est circulaire.)

y a-t-il un annuaire dont l'existence est garantie? Oui, mais typiquement un seul: le répertoire supérieur du système de fichiers racine (tel que défini ci-dessus).

les noms de chemins de passage qui résident dans un autre répertoire (et qui ne sont donc pas garantis) à os.stat() ou os.lstat() invitent à des conditions de race, même si ce répertoire a déjà été testé pour exister. Pourquoi? Parce que les processus externes ne peuvent pas être empêchés de supprimer simultanément ce répertoire après ce test a été effectué mais avant ce nom de chemin est passé à os.stat() ou os.lstat() . Lâchez les chiens de l'esprit-fellating la folie!

il existe un avantage secondaire important à l'approche ci-dessus ainsi: sécurité. (n'est-ce pas que nice?) Plus précisément:

en face Avant, les demandes de validation arbitraire des chemins d'accès à partir de sources non fiables, en passant dans de tels chemins d'accès à os.stat() ou os.lstat() sont susceptibles à des attaques de déni de Service (DoS) et d'autres shenanigans chapeau noir. Les utilisateurs malveillants peuvent tenter de valider à plusieurs reprises des noms de chemins résidant sur des systèmes de fichiers connus pour être vétustes ou lents (par exemple, les actions NFS Samba); dans ce cas, statter aveuglément les noms de chemins entrants est susceptible d'échouer éventuellement avec les temps morts de connexion ou de consommer plus de temps et de ressources que votre faible capacité à résister au chômage.

le l'approche ci-dessus permet d'éviter cela en validant uniquement les composants de chemin d'un chemin par rapport au répertoire racine du système de fichiers racine. (Si même qui est périmé, lent, ou inaccessible, vous avez de plus gros problèmes que la validation du chemin.)

perdu? Super. commençons. (Python 3 suppose. Voir "Ce qui Est Fragile Espoir pour les 300, leycec ?")

import errno, os

# Sadly, Python fails to provide the following magic number for us.
ERROR_INVALID_NAME = 123
'''
Windows-specific error code indicating an invalid pathname.

See Also
----------
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx
    Official listing of all such codes.
'''

def is_pathname_valid(pathname: str) -> bool:
    '''
    `True` if the passed pathname is a valid pathname for the current OS;
    `False` otherwise.
    '''
    # If this pathname is either not a string or is but is empty, this pathname
    # is invalid.
    try:
        if not isinstance(pathname, str) or not pathname:
            return False

        # Strip this pathname's Windows-specific drive specifier (e.g., `C:\`)
        # if any. Since Windows prohibits path components from containing `:`
        # characters, failing to strip this `:`-suffixed prefix would
        # erroneously invalidate all valid absolute Windows pathnames.
        _, pathname = os.path.splitdrive(pathname)

        # Directory guaranteed to exist. If the current OS is Windows, this is
        # the drive to which Windows was installed (e.g., the "%HOMEDRIVE%"
        # environment variable); else, the typical root directory.
        root_dirname = os.environ.get('HOMEDRIVE', 'C:') \
            if sys.platform == 'win32' else os.path.sep
        assert os.path.isdir(root_dirname)   # ...Murphy and her ironclad Law

        # Append a path separator to this directory if needed.
        root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep

        # Test whether each path component split from this pathname is valid or
        # not, ignoring non-existent and non-readable path components.
        for pathname_part in pathname.split(os.path.sep):
            try:
                os.lstat(root_dirname + pathname_part)
            # If an OS-specific exception is raised, its error code
            # indicates whether this pathname is valid or not. Unless this
            # is the case, this exception implies an ignorable kernel or
            # filesystem complaint (e.g., path not found or inaccessible).
            #
            # Only the following exceptions indicate invalid pathnames:
            #
            # * Instances of the Windows-specific "WindowsError" class
            #   defining the "winerror" attribute whose value is
            #   "ERROR_INVALID_NAME". Under Windows, "winerror" is more
            #   fine-grained and hence useful than the generic "errno"
            #   attribute. When a too-long pathname is passed, for example,
            #   "errno" is "ENOENT" (i.e., no such file or directory) rather
            #   than "ENAMETOOLONG" (i.e., file name too long).
            # * Instances of the cross-platform "OSError" class defining the
            #   generic "errno" attribute whose value is either:
            #   * Under most POSIX-compatible OSes, "ENAMETOOLONG".
            #   * Under some edge-case OSes (e.g., SunOS, *BSD), "ERANGE".
            except OSError as exc:
                if hasattr(exc, 'winerror'):
                    if exc.winerror == ERROR_INVALID_NAME:
                        return False
                elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}:
                    return False
    # If a "TypeError" exception was raised, it almost certainly has the
    # error message "embedded NUL character" indicating an invalid pathname.
    except TypeError as exc:
        return False
    # If no exception was raised, all path components and hence this
    # pathname itself are valid. (Praise be to the curmudgeonly python.)
    else:
        return True
    # If any other exception was raised, this is an unrelated fatal issue
    # (e.g., a bug). Permit this exception to unwind the call stack.
    #
    # Did we mention this should be shipped with Python already?

Faire. ne louchez pas à ce code. ( Il mord. )

Question n ° 2: Existence D'un chemin D'accès non valide ou possibilité de création, Hein?

tester l'existence ou la créabilité de noms de chemins éventuellement invalides est, compte tenu de la solution ci-dessus, la plupart du temps trivial. La petite clé ici est d'appeler la fonction définie précédemment avant tester le chemin passé:

def is_path_creatable(pathname: str) -> bool:
    '''
    `True` if the current user has sufficient permissions to create the passed
    pathname; `False` otherwise.
    '''
    # Parent directory of the passed path. If empty, we substitute the current
    # working directory (CWD) instead.
    dirname = os.path.dirname(pathname) or os.getcwd()
    return os.access(dirname, os.W_OK)

def is_path_exists_or_creatable(pathname: str) -> bool:
    '''
    `True` if the passed pathname is a valid pathname for the current OS _and_
    either currently exists or is hypothetically creatable; `False` otherwise.

    This function is guaranteed to _never_ raise exceptions.
    '''
    try:
        # To prevent "os" module calls from raising undesirable exceptions on
        # invalid pathnames, is_pathname_valid() is explicitly called first.
        return is_pathname_valid(pathname) and (
            os.path.exists(pathname) or is_path_creatable(pathname))
    # Report failure on non-fatal filesystem complaints (e.g., connection
    # timeouts, permissions issues) implying this path to be inaccessible. All
    # other exceptions are unrelated fatal issues and should not be caught here.
    except OSError:
        return False

Fait et fait. Sauf pas tout à fait.

Question #3: Existence D'un chemin D'accès non valide ou possibilité D'écriture sur Windows

Il existe une mise en garde. Bien sûr, il n'.

comme le Officiel os.access() la documentation admet:

Note: les opérations d'E/S peuvent échouer même si os.access() indique qu'ils réussiraient, en particulier pour les opérations sur les systèmes de fichiers réseau qui peuvent avoir des permissions sémantiques au-delà du modèle habituel de permission-bit POSIX.

à la surprise générale, Windows est le suspect habituel ici. Grâce à l'utilisation extensive des listes de contrôle d'accès (ACL) sur les systèmes de fichiers NTFS, le modèle simple POSIX permission-bit correspond mal à la réalité Windows sous-jacente. Bien que ce (sans doute) N'est pas la faute de Python, il pourrait néanmoins être une préoccupation pour les applications compatibles Windows.

si c'est vous, une alternative plus robuste est recherchée. Si le chemin réussi n'existe pas , nous essayons plutôt de créer un fichier temporaire garanti pour être immédiatement supprimé dans le répertoire parent de ce chemin – un test plus portable (si coûteux) de la créabilité:

import os, tempfile

def is_path_sibling_creatable(pathname: str) -> bool:
    '''
    `True` if the current user has sufficient permissions to create **siblings**
    (i.e., arbitrary files in the parent directory) of the passed pathname;
    `False` otherwise.
    '''
    # Parent directory of the passed path. If empty, we substitute the current
    # working directory (CWD) instead.
    dirname = os.path.dirname(pathname) or os.getcwd()

    try:
        # For safety, explicitly close and hence delete this temporary file
        # immediately after creating it in the passed path's parent directory.
        with tempfile.TemporaryFile(dir=dirname): pass
        return True
    # While the exact type of exception raised by the above function depends on
    # the current version of the Python interpreter, all such types subclass the
    # following exception superclass.
    except EnvironmentError:
        return False

def is_path_exists_or_creatable_portable(pathname: str) -> bool:
    '''
    `True` if the passed pathname is a valid pathname on the current OS _and_
    either currently exists or is hypothetically creatable in a cross-platform
    manner optimized for POSIX-unfriendly filesystems; `False` otherwise.

    This function is guaranteed to _never_ raise exceptions.
    '''
    try:
        # To prevent "os" module calls from raising undesirable exceptions on
        # invalid pathnames, is_pathname_valid() is explicitly called first.
        return is_pathname_valid(pathname) and (
            os.path.exists(pathname) or is_path_sibling_creatable(pathname))
    # Report failure on non-fatal filesystem complaints (e.g., connection
    # timeouts, permissions issues) implying this path to be inaccessible. All
    # other exceptions are unrelated fatal issues and should not be caught here.
    except OSError:
        return False

noter, cependant, que même ce ne peut pas suffire.

grâce au contrôle D'accès utilisateur (UAC), le Windows Vista toujours inimitable et toutes les itérations ultérieures de celui-ci mensonge flagrant au sujet des permissions se rapportant aux annuaires de système. Lorsque des utilisateurs non-administrateurs tentent de créer des fichiers dans les répertoires canoniques C:\Windows ou C:\Windows\system32 , UAC permet superficiellement à l'utilisateur de le faire tandis que en fait isolant tous les fichiers créés dans un "virtuel" Magasin" dans le profil de cet utilisateur. (Qui aurait pu imaginer que tromper les utilisateurs aurait des conséquences néfastes à long terme?)

c'est fou. C'est Windows.

Prouver

osons-nous? Il est temps de tester les tests ci-dessus.

étant donné que NULL est le seul caractère interdit dans les noms de chemin sur les systèmes de fichiers orientés UNIX, nous allons utiliser cet argument pour démontrer la froide, dure vérité – ignoring fenêtres non-ignorables shenanigans, qui franchement m'ennuient et m'irritent dans la même mesure:

>>> print('"foo.bar" valid? ' + str(is_pathname_valid('foo.bar')))
"foo.bar" valid? True
>>> print('Null byte valid? ' + str(is_pathname_valid('\x00')))
Null byte valid? False
>>> print('Long path valid? ' + str(is_pathname_valid('a' * 256)))
Long path valid? False
>>> print('"/dev" exists or creatable? ' + str(is_path_exists_or_creatable('/dev')))
"/dev" exists or creatable? True
>>> print('"/dev/foo.bar" exists or creatable? ' + str(is_path_exists_or_creatable('/dev/foo.bar')))
"/dev/foo.bar" exists or creatable? False
>>> print('Null byte exists or creatable? ' + str(is_path_exists_or_creatable('\x00')))
Null byte exists or creatable? False

au-Delà de la raison. Au-delà de la douleur. Vous trouverez des problèmes de portabilité Python.

79
répondu Cecil Curry 2017-05-23 12:34:37
if os.path.exists(filePath):
    #the file is there
elif os.access(os.path.dirname(filePath), os.W_OK):
    #the file does not exists but write privileges are given
else:
    #can not write there

notez que path.exists peut échouer pour plus de raisons que the file is not there donc vous pourriez avoir à faire des tests plus précis comme des tests si le répertoire containing existe et ainsi de suite.


après ma discussion avec L'OP, il s'est avéré, que le problème principal semble être, que le nom du fichier pourrait contenir des caractères qui ne sont pas autorisés par le système de fichiers. Bien sûr, ils doivent être enlevés, mais L'OP veut maintenir autant d'humain readablitiy que le système de fichiers permet.

malheureusement je ne connais pas de bonne solution pour cela. Toutefois, la réponse de Cecil Curry examine de plus près la détection du problème.

32
répondu Nobody 2015-12-08 09:06:59
open(filename,'r')   #2nd argument is r and not w

ouvrira le fichier ou donnera une erreur s'il n'existe pas. S'il y a une erreur, alors vous pouvez essayer d'écrire sur le chemin, si vous ne pouvez pas alors vous obtenez une seconde erreur

try:
    open(filename,'r')
    return True
except IOError:
    try:
        open(filename, 'w')
        return True
    except IOError:
        return False

ont également un regard ici A propos des permissions sous windows

4
répondu vikki 2017-05-23 11:47:28

avec Python 3, Que diriez-vous de:

try:
    with open(filename, 'x') as tempfile: # OSError if file exists or is invalid
        pass
except OSError:
    # handle error here

avec l'option "x", nous n'avons pas à nous soucier des conditions de course. Voir la documentation ici .

maintenant, cela créera un fichier temporaire de très courte durée s'il n'existe pas déjà - à moins que le nom ne soit invalide. Si vous pouvez vivre avec ça, ça simplifie beaucoup les choses.

2
répondu Stephen Miller 2018-01-29 10:32:52

essayer os.path.exists cela permettra de vérifier le chemin d'accès et le retour True si existe et False si pas.

-2
répondu Nilesh 2012-03-02 11:33:09