J'ai un chemin (y compris le répertoire et le nom du fichier).
J'ai besoin de 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 contient des caractères Unicode .
Il est prudent de supposer que le segment de répertoire du chemin est valide et accessible ( j'essayais de rendre la question plus applicable de manière générale, et apparemment, j'étais trop loin ).
Je ne veux vraiment pas avoir à échapper à quoi que ce soit à moins d'y être obligé .
Je publierais certains des exemples de caractères avec lesquels je suis en train de traiter, mais apparemment, ils sont automatiquement supprimés par le système d'échange de piles. Quoi qu'il en soit, je veux garder les entités Unicode standard comme ö
, et n'échapper qu'aux choses qui ne sont pas valides dans un nom de fichier.
Voici le piège. Il se peut (ou non) déjà un fichier sur la cible du chemin. Je dois conserver ce fichier s'il existe et ne pas créer de fichier s'il n'existe pas.
Fondamentalement, je veux vérifier si je pourrais écrire sur un chemin sans ouvrir réellement le chemin pour l'écriture (et la création automatique de fichiers / le sabotage de fichiers que cela implique généralement).
En tant que tel:
try:
open(filename, 'w')
except OSError:
# handle error here
N'est pas acceptable, car cela écrasera le fichier existant, que je ne veux pas toucher (s'il y en a), ou créera ledit fichier si ce n'est pas le cas.
Je sais que je peux faire:
if not os.access(filePath, os.W_OK):
try:
open(filePath, 'w').close()
os.unlink(filePath)
except OSError:
# handle error here
Mais cela créera le fichier à la filePath
, ce que je devrais alors faire os.unlink
.
En fin de compte, il semble que cela passe 6 ou 7 lignes pour faire quelque chose qui devrait être aussi simple os.isvalidpath(filePath)
ou similaire.
En passant, j'ai besoin que cela fonctionne sur (au moins) Windows et MacOS, donc j'aimerais éviter les trucs spécifiques à la plate-forme.
''
la source
Réponses:
tl; dr
Appelez la
is_path_exists_or_creatable()
fonction définie ci-dessous.Strictement Python 3. C'est comme ça que nous roulons.
Un conte de deux questions
La question "Comment tester la validité des chemins et, pour les chemins valides, l'existence ou l'écriture de ces chemins?" est clairement deux questions distinctes. Les deux sont intéressants, et ni l'un ni l'autre n'ont reçu de réponse vraiment satisfaisante ici ... ou, bien, partout où je pourrais grep.
Vikki de réponse HEWS probablement le plus proche, mais présente les inconvénients remarquables:
On va réparer tout ça.
Question n ° 0: Quelle est encore la validité du chemin d'accès?
Avant de lancer nos fragiles combinaisons de viande dans les moshpits de douleur criblés de python, nous devrions probablement définir ce que nous entendons par «validité de chemin». Qu'est-ce qui définit la validité, exactement?
Par «validité de chemin», nous entendons l' exactitude syntaxique d'un chemin par rapport au système de fichiers racine du système actuel - indépendamment du fait que ce chemin ou ses répertoires parents existent physiquement. Un chemin d'accès est syntaxiquement correct selon cette définition s'il est conforme à toutes les exigences syntaxiques du système de fichiers racine.
Par «système de fichiers racine», nous entendons:
/
).%HOMEDRIVE%
, la lettre de lecteur avec le suffixe deux-points contenant l'installation actuelle de Windows (généralement mais pas nécessairementC:
).La signification de «correction syntaxique» dépend à son tour du type de système de fichiers racine. Pour
ext4
(et la plupart des systèmes de fichiers compatibles POSIX mais pas tous), un chemin d'accès est syntaxiquement correct si et seulement si ce chemin:\x00
à- dire en Python). C'est une exigence absolue pour tous les systèmes de fichiers compatibles POSIX.'a'*256
en Python). Un composant de chemin est une plus longue sous - chaîne d'un chemin ne contenant pas de/
caractère (par exemplebergtatt
,ind
,i
, etfjeldkamrene
dans le chemin d' accès/bergtatt/ind/i/fjeldkamrene
).Exactitude syntaxique. Système de fichiers racine. C'est ça.
Question n ° 1: Comment allons-nous maintenant faire la validité des chemins d'accès?
La validation des chemins d'accès en Python n'est étonnamment pas intuitive. Je suis totalement d'accord avec Fake Name ici: le
os.path
package officiel devrait fournir une solution prête à l'emploi pour cela. Pour des raisons inconnues (et probablement non convaincantes), ce n'est pas le cas. Heureusement, le déroulement de votre propre solution ad hoc n'est pas si déchirant ...OK, c'est en fait. C'est velu; c'est dégueulasse; il glousse probablement en ronflant et glousse quand il brille. Mais qu'est-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 de boutique de haut niveau. La norme
os.stat()
et lesos.lstat()
fonctions lèvent les exceptions suivantes lors de la transmission de chemins d'accès non valides:FileNotFoundError
.WindowsError
dont l'winerror
attribut est123
(c'est-à-direERROR_INVALID_NAME
).'\x00'
), des instances deTypeError
.OSError
dont l'errcode
attribut est:errno.ERANGE
. (Cela semble être un bogue au niveau du système d'exploitation, autrement appelé "interprétation sélective" du standard POSIX.)errno.ENAMETOOLONG
.Fondamentalement, cela implique que seuls les chemins résidant dans les répertoires existants sont validables. Les fonctions
os.stat()
etos.lstat()
lèvent desFileNotFoundError
exceptions génériques lors de la transmission de noms de chemins résidant dans des répertoires non existants, que ces chemins soient invalides ou non. L'existence de l'annuaire a priorité sur l'invalidité du chemin.Cela signifie-t-il que les noms de chemins résidant dans des répertoires non existants ne sont pas validables? Oui - sauf si nous modifions ces chemins pour qu'ils résident dans des répertoires existants. Mais est-ce même faisable en toute sécurité? La modification d'un chemin ne devrait-il pas nous empêcher de valider le chemin d'origine?
Pour répondre à cette question, rappelez ci-dessus que les noms de chemin syntaxiquement corrects sur le
ext4
système de fichiers ne contiennent aucun composant de chemin (A) contenant des octets nuls ou (B) de plus de 255 octets de longueur. Par conséquent, unext4
chemin est valide si et seulement si tous les composants de chemin dans ce chemin sont valides. Ceci est vrai pour la plupart des systèmes de fichiers d'intérêt réels .Cette vision pédante nous aide-t-elle réellement? Oui. Cela réduit le plus gros problème de validation du chemin complet d'un seul coup au problème plus petit de valider uniquement tous les composants du chemin dans ce chemin. Tout chemin arbitraire peut être validé (indépendamment du fait que ce chemin réside ou non dans un répertoire existant) de manière multiplateforme en suivant l'algorithme suivant:
/troldskog/faren/vild
dans la liste['', 'troldskog', 'faren', 'vild']
)./troldskog
).os.stat()
ouos.lstat()
. Si ce chemin d'accès et donc ce composant n'est pas valide, cet appel est garanti pour déclencher une exception exposant le type d'invalidité plutôt qu'uneFileNotFoundError
exception générique . Pourquoi? Parce que ce chemin réside dans un répertoire existant. (La logique circulaire est circulaire.)Existe-t-il un annuaire garanti? Oui, mais typiquement un seul: le répertoire le plus haut du système de fichiers racine (tel que défini ci-dessus).
Passer des noms de chemins résidant dans n'importe quel autre répertoire (et donc pas garanti d'exister) à
os.stat()
ouos.lstat()
invite des conditions de concurrence, même si ce répertoire a été précédemment 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 que ce test a été effectué mais avant que ce chemin ne soit passé àos.stat()
ouos.lstat()
. Libérez les chiens de la folie époustouflante!L'approche ci-dessus présente également un avantage secondaire substantiel: la sécurité. (Est -ce pas ce bien?) Plus précisément:
L'approche ci-dessus évite cela en validant uniquement les composants de chemin d'un chemin par rapport au répertoire racine du système de fichiers racine. (Même si cela est obsolète, lent ou inaccessible, vous avez des problèmes plus importants que la validation des chemins.)
Perdu? Génial. Commençons. (Python 3 supposé. Voir "Qu'est-ce que Fragile Hope pour 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://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- 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?
Terminé. Ne plissez pas les yeux sur ce code. ( Ça mord. )
Question n ° 2: Peut-être une existence ou une créabilité de chemin d'accès invalide, hein?
Tester l'existence ou la créabilité de chemins d'accès éventuellement invalides est, étant donné la solution ci-dessus, la plupart du temps trivial. La petite clé ici est d'appeler la fonction précédemment définie avant de 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 n ° 3: Peut-être une existence de nom de chemin ou une écriture non valide sous Windows
Il existe une mise en garde. Bien sûr que oui.
Comme l' admet la
os.access()
documentation officielle :Sans surprise, 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 simpliste de bits d'autorisation POSIX correspond mal à la réalité Windows sous-jacente. Bien que ce ne soit (sans doute) pas la faute de Python, cela pourrait néanmoins être préoccupant pour les applications compatibles Windows.
Si c'est vous, une alternative plus robuste est souhaitée. Si le chemin passé n'existe pas , nous essayons à la place de créer un fichier temporaire garanti d'être immédiatement supprimé dans le répertoire parent de ce chemin - un test de créabilité plus portable (bien que coûteux):
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
Notez, cependant, que même cela peut ne 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 mentent de manière flagrante sur les autorisations relatives aux répertoires système. Lorsque des utilisateurs non-administrateurs tentent de créer des fichiers dans le répertoire canonique
C:\Windows
ou dans lesC:\Windows\system32
répertoires, l'UAC autorise superficiellement l'utilisateur à le faire tout en isolant en fait tous les fichiers créés dans un "magasin virtuel" 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.
Prouve le
Osons-nous? Il est temps de tester les tests ci-dessus.
Étant donné que NULL est le seul caractère interdit dans les chemins d'accès sur les systèmes de fichiers orientés UNIX, exploitons-le pour démontrer la froide et dure vérité - en ignorant les manigances Windows non ignorables, qui m'ennuient et me mettent en colère dans une égale 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.
la source
is_
. C'est mon défaut de caractère. Néanmoins, dûment noté: vous ne pouvez pas plaire à tout le monde, et parfois vous ne pouvez plaire à personne. ;)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 cela
path.exists
peut échouer pour plus de raisons que simplementthe file is not there
pour que vous deviez faire des tests plus fins comme tester si le répertoire contenant existe, etc.Après ma discussion avec l'OP, il s'est avéré que le problème principal semble être que le nom du fichier peut contenir des caractères qui ne sont pas autorisés par le système de fichiers. Bien sûr, ils doivent être supprimés mais l'OP souhaite conserver autant de lisibilité humaine que le système de fichiers le permet.
Malheureusement, je ne connais aucune bonne solution pour cela. Cependant, la réponse de Cecil Curry examine de plus près la détection du problème.
la source
or can be created
eh bien, je n'ai pas lu cela de votre question. La lecture des autorisations dépendra dans une certaine mesure de la plate-forme.os.path.exists(filePath)
lève techniquement des exceptions sur des chemins non valides, ces exceptions devraient être explicitement interceptées et différenciées des autres exceptions non liées. De plus, le même appel revientFalse
sur les chemins existants sur lesquels l'utilisateur actuel n'a pas les autorisations de lecture. En bref, la méchanceté.Avec Python 3, que diriez-vous:
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 non plus à nous soucier des conditions de course. Consultez 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 cela, cela simplifie beaucoup les choses.
la source
open(filename,'r') #2nd argument is r and not w
va ouvrir le fichier ou donner une erreur s'il n'existe pas. S'il y a une erreur, vous pouvez essayer d'écrire dans le chemin, si vous ne pouvez pas, vous obtenez une deuxième erreur
try: open(filename,'r') return True except IOError: try: open(filename, 'w') return True except IOError: return False
Jetez également un œil ici sur les autorisations sur Windows
la source
tempfile.TemporaryFile()
qui détruira automatiquement le fichier temporaire lorsqu'il sort de la portée.os.path.join
, donc je n'ai pas de problèmes d'échappatoire. De plus, je n'ai pas vraiment de problèmes d' autorisation de répertoire . J'ai des problèmes de nom de répertoire (et de nom de fichier) .filename
contient des caractères non valides. J'ai édité la réponseessayez
os.path.exists
ceci vérifiera le chemin et retourneraTrue
s'il existe etFalse
sinon.la source