Question

J'ai une chaîne que je veux utiliser comme nom de fichier, donc je veux supprimer tous les caractères qui ne seraient pas autorisés dans les noms de fichiers, en utilisant Python.

Je préfère être strict que le contraire, alors supposons que je ne souhaite conserver que des lettres, des chiffres et un petit ensemble de caractères tels que "_-. ()" . Quelle est la solution la plus élégante?

Le nom de fichier doit être valide sur plusieurs systèmes d'exploitation (Windows, Linux et Mac OS) - il s'agit d'un fichier MP3 de ma bibliothèque avec le titre de la chanson comme nom de fichier, puis partagé et sauvegardé entre 3 ordinateurs.

Était-ce utile?

La solution 7

C’est la solution que j’ai finalement utilisée:

import unicodedata

validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)

def removeDisallowedFilenameChars(filename):
    cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
    return ''.join(c for c in cleanedFilename if c in validFilenameChars)

L’appel unicodedata.normalize remplace les caractères accentués par l’équivalent non accentué, ce qui est mieux que de simplement les supprimer. Après cela, tous les caractères non autorisés sont supprimés.

Ma solution ne pré-ajoute pas une chaîne connue pour éviter les noms de fichiers non autorisés, car je sais qu'elles ne peuvent pas apparaître compte tenu de mon format de nom de fichier particulier. Une solution plus générale devrait le faire.

Autres conseils

Vous pouvez consulter le framework Django pour savoir comment ils ont créé un " slug " à partir de texte arbitraire. Un slug est convivial pour les URL et les noms de fichiers.

Les outils de texte Django définissent une fonction, < code> slugify () , c’est probablement le standard de référence pour ce genre de chose. Leur code est essentiellement le suivant.

def slugify(value):
    """
    Normalizes string, converts to lowercase, removes non-alpha characters,
    and converts spaces to hyphens.
    """
    import unicodedata
    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
    value = unicode(re.sub('[-\s]+', '-', value))

Il y a plus, mais je l'ai laissé de côté, car il ne traite pas de la slugification, mais échapper.

Cette approche de liste blanche (c'est-à-dire, n'autorisant que les caractères présents dans valid_chars) fonctionnera s'il n'y a pas de limites au formatage des fichiers ou à une combinaison de caractères valides qui sont illégaux (comme " .. "), par exemple. Par exemple, ce que vous dites autoriserait un nom de fichier nommé " . txt " que je pense n'est pas valable sur Windows. Comme il s’agit de l’approche la plus simple, j’essayerais de supprimer les espaces blancs de valid_chars et d’ajouter une chaîne valide connue en cas d’erreur, toute autre approche devra savoir ce qu’il est permis de gérer avec Limitations de nommage des fichiers Windows et donc beaucoup plus complexe .

>>> import string
>>> valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
>>> valid_chars
'-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
>>> filename = "This Is a (valid) - filename%<*>amp;$ .txt"
>>> ''.join(c for c in filename if c in valid_chars)
'This Is a (valid) - filename .txt'

Vous pouvez utiliser la compréhension de liste avec les méthodes de chaîne.

>>> s
'foo-bar#baz?qux@127/\\9]'
>>> "".join(x for x in s if x.isalnum())
'foobarbazqux1279'

Quelle est la raison d'utiliser les chaînes en tant que noms de fichiers? Si la lisibilité humaine n’est pas un facteur, j’irais avec le module base64 qui peut produire des chaînes sûres pour le système de fichiers. Il ne sera pas lisible, mais vous n’aurez pas à faire face à des collisions et il est réversible.

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

Mettre à jour : modifié en fonction du commentaire de Matthew.

Juste pour compliquer encore les choses, il n’est pas garanti que vous obtiendrez un nom de fichier valide simplement en supprimant les caractères non valides. Étant donné que les caractères autorisés diffèrent en fonction du nom de fichier, une approche conservatrice pourrait transformer un nom valide en un nom invalide. Vous voudrez peut-être ajouter un traitement spécial dans les cas où:

  • La chaîne contient tous les caractères non valides (vous laissant une chaîne vide)

  • Vous vous retrouvez avec une chaîne avec une signification spéciale, par exemple ". ou ".."

  • Sous Windows, certains noms de périphérique sont réservés. Par exemple, vous ne pouvez pas créer de fichier nommé "nul", "nul.txt". (ou rien de fait.) Les noms réservés sont:

    CON, PRN, AUX, NUL, COM1, COM2, COM4, ??COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT4, LPT5, LPT6, LPT7, LPT8 et LPT9

Vous pouvez probablement contourner ces problèmes en ajoutant une chaîne aux noms de fichiers qui ne peuvent jamais aboutir à l'un de ces cas, et en supprimant les caractères non valides.

Il existe un beau projet sur Github intitulé python-slugify :

Installer:

pip install python-slugify

Ensuite, utilisez:

>>> from slugify import slugify
>>> txt = "This\ is/ a%#$ test ---"
>>> slugify(txt)
'this-is-a-test'

Tout comme pour S.Lott , vous pouvez consulter le Django Framework explique comment ils convertissent une chaîne en un nom de fichier valide.

La version la plus récente et mise à jour se trouve dans utils / text.py, et définit "get_valid_filename", ce qui est comme suit:

def get_valid_filename(s):
    s = str(s).strip().replace(' ', '_')
    return re.sub(r'(?u)[^-\w.]', '', s)

(Voir https://github.com/django/ Django / blob / maître / Django / utils / text.py )

N'oubliez pas qu'il n'y a aucune restriction sur les noms de fichiers sur les systèmes Unix autres que

.
  • Il ne doit pas contenir \ 0
  • Il ne peut pas contenir /

Tout le reste est un jeu juste.

$ touch "
> even multiline
> haha
> ^[[31m red ^[[0m
> evil"
$ ls -la 
-rw-r--r--       0 Nov 17 23:39 ?even multiline?haha??[31m red ?[0m?evil
$ ls -lab
-rw-r--r--       0 Nov 17 23:39 \neven\ multiline\nhaha\n\033[31m\ red\ \033[0m\nevil
$ perl -e 'for my $i ( glob(q{./*even*}) ){ print $i; } '
./
even multiline
haha
 red 
evil

Oui, je viens de stocker les codes de couleur ANSI dans un nom de fichier et de les appliquer.

Pour vous divertir, insérez un caractère BEL dans un nom de répertoire et observez le plaisir qui en découle lorsque vous y créez un CD;)

En une ligne:

valid_file_name = re.sub('[^\w_.)( -]', '', any_string)

vous pouvez également mettre le caractère '_' pour le rendre plus lisible (par exemple, en cas de remplacement de barres obliques)

Vous pouvez utiliser la méthode re.sub () pour remplacer tout ce qui n’est pas "filelike". Mais en réalité, chaque caractère pourrait être valide; donc, il n'y a pas de fonctions prédéfinies (je crois) pour le faire.

import re

str = "File!name?.txt"
f = open(os.path.join("/tmp", re.sub('[^-a-zA-Z0-9_.() ]+', '', str))

Entraînerait un traitement de fichier vers /tmp/nom de fichier.txt.

>>> import string
>>> safechars = bytearray(('_-.()' + string.digits + string.ascii_letters).encode())
>>> allchars = bytearray(range(0x100))
>>> deletechars = bytearray(set(allchars) - set(safechars))
>>> filename = u'#ab\xa0c.$%.txt'
>>> safe_filename = filename.encode('ascii', 'ignore').translate(None, deletechars).decode()
>>> safe_filename
'abc..txt'

Il ne gère pas les chaînes vides, les noms de fichiers spéciaux ('nul', 'con', etc.).

Bien que vous deviez faire attention. Ce n'est pas clairement dit dans votre intro, si vous ne regardez que la langue latine. Certains mots peuvent perdre leur signification ou une autre signification si vous les désinfectez avec des caractères ascii uniquement.

Imaginez que vous ayez " for & # 234; t po & # 233; sie " (poésie forestière), votre désinfection pourrait donner "fort-posie" (fort + quelque chose sans signification)

Pire si vous devez composer avec des caractères chinois.

" 19979; & # 21271; & # 27810; " votre système pourrait finir par faire " --- " qui est voué à l'échec après un certain temps et pas très utile. Donc, si vous ne traitez que des fichiers, j’encourage à les appeler soit une chaîne générique que vous contrôlez, soit à conserver les caractères tels quels. Pour les URI, à peu près les mêmes.

Pourquoi ne pas simplement envelopper le & os; osopen " avec un essai / sauf et laisser le système d'exploitation sous-jacent déterminer si le fichier est valide?

Cela semble beaucoup moins de travail et est valable quel que soit le système d'exploitation que vous utilisez.

Un autre problème que les autres commentaires n’ont pas encore abordé est la chaîne vide, qui n’est évidemment pas un nom de fichier valide. Vous pouvez également vous retrouver avec une chaîne vide en supprimant trop de caractères.

Qu'en est-il des noms de fichiers réservés de Windows et des problèmes de points, la réponse la plus sûre à la question «Comment normaliser un nom de fichier valide à partir d'une entrée utilisateur arbitraire?» est «Ne tentez même pas d'essayer»: si vous pouvez en trouver Pour l'éviter (par exemple, en utilisant des clés primaires entières d'une base de données comme noms de fichiers), faites-le.

Si vous devez, et que vous avez vraiment besoin d'autoriser les espaces et "." pour les extensions de fichier dans le nom, essayez quelque chose du genre:

import re
badchars= re.compile(r'[^A-Za-z0-9_. ]+|^\.|\.$|^ | $|^

Même cela ne peut pas être garanti, surtout sur des systèmes d’exploitation inattendus - par exemple, RISC OS déteste les espaces et utilise ‘.’ comme séparateur de répertoire.

) badnames= re.compile(r'(aux|com[1-9]|con|lpt[1-9]|prn)(\.|$)') def makeName(s): name= badchars.sub('_', s) if badnames.match(name): name= '_'+name return name

Même cela ne peut pas être garanti, surtout sur des systèmes d’exploitation inattendus - par exemple, RISC OS déteste les espaces et utilise ‘.’ comme séparateur de répertoire.

J'ai bien aimé l'approche python-slugify ici, mais elle faisait aussi des ravages, ce qui n'était pas souhaitable. Je l’ai donc optimisé pour le téléchargement d’un nom de fichier propre sur s3 de la manière suivante:

pip install python-slugify

Exemple de code:

s = 'Very / Unsafe / file\nname hähä \n\r .txt'
clean_basename = slugify(os.path.splitext(s)[0])
clean_extension = slugify(os.path.splitext(s)[1][1:])
if clean_extension:
    clean_filename = '{}.{}'.format(clean_basename, clean_extension)
elif clean_basename:
    clean_filename = clean_basename
else:
    clean_filename = 'none' # only unclean characters

Sortie:

>>> clean_filename
'very-unsafe-file-name-haha.txt'

C’est tellement sûr que cela fonctionne avec les noms de fichiers sans extension et même avec les noms de fichiers contenant des caractères non sécurisés (le résultat est aucun ici).

La plupart de ces solutions ne fonctionnent pas.

'/ hello / world' - > 'helloworld'

'/ helloworld' / - > 'helloworld'

Ce n’est pas ce que vous voulez en général, disons que vous enregistrez le code HTML pour chaque lien, vous allez écraser le code HTML pour une page Web différente.

Je décapite un dict tel que:

{'helloworld': 
    (
    {'/hello/world': 'helloworld', '/helloworld/': 'helloworld1'},
    2)
    }

2 représente le numéro à ajouter au nom de fichier suivant.

Je cherche le nom du fichier à chaque fois dans le dict. Si ce n'est pas le cas, j'en crée un nouveau, en ajoutant le nombre maximal si nécessaire.

Ce n'est pas exactement ce que l'OP demandait, mais c'est ce que j'utilise parce que j'ai besoin de conversions uniques et réversibles:

# p3 code
def safePath (url):
    return ''.join(map(lambda ch: chr(ch) if ch in safePath.chars else '%%%02x' % ch, url.encode('utf-8')))
safePath.chars = set(map(lambda x: ord(x), '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-_ .'))

Le résultat est "quelque peu". lisible, au moins d’un point de vue administrateur système.

Je suis sûr que ce n'est pas une bonne réponse, car cela modifie la chaîne sur laquelle elle est bouclée, mais cela semble fonctionner correctement:

import string
for chr in your_string:
 if chr == ' ':
   your_string = your_string.replace(' ', '_')
 elif chr not in string.ascii_letters or chr not in string.digits:
    your_string = your_string.replace(chr, '')

MISE À JOUR

Tous les liens brisés de manière irréparable dans cette réponse de 6 ans.

De plus, je ne le ferais plus de cette façon plus, il suffit de base64 d’encoder ou de supprimer les caractères non sécurisés. Exemple Python 3:

import re
t = re.compile("[a-zA-Z0-9.,_-]")
unsafe = "abc∂éåß®∆˚˙©¬ñ√ƒµ©∆∫ø"
safe = [ch for ch in unsafe if t.match(ch)]
# => 'abc'

Avec base64 , vous pouvez encoder et décoder afin de pouvoir récupérer à nouveau le nom de fichier d'origine.

Mais selon le cas d'utilisation, il peut être préférable de générer un nom de fichier aléatoire et de stocker les métadonnées dans un fichier ou une base de données distinct.

from random import choice
from string import ascii_lowercase, ascii_uppercase, digits
allowed_chr = ascii_lowercase + ascii_uppercase + digits

safe = ''.join([choice(allowed_chr) for _ in range(16)])
# => 'CYQ4JDKE9JfcRzAZ'

RÉPONSE LINKROTTEN ORIGINALE :

Le projet bobcat contient un module python qui ne fait que cela.

Ce n'est pas tout à fait robuste, consultez ce post et cette répondre .

Ainsi, comme indiqué précédemment: l'encodage base64 est probablement une meilleure idée si la lisibilité n'a pas d'importance.

Je me rends compte qu'il y a beaucoup de réponses, mais elles reposent principalement sur des expressions régulières ou des modules externes. J'aimerais donc ajouter ma propre réponse. Une fonction pure python, aucun module externe nécessaire, aucune expression régulière utilisée. Mon approche n’est pas de nettoyer les caractères non valides, mais de n’autoriser que les caractères valides.

def normalizefilename(fn):
    validchars = "-_.() "
    out = ""
    for c in fn:
      if str.isalpha(c) or str.isdigit(c) or (c in validchars):
        out += c
      else:
        out += "_"
    return out    

si vous le souhaitez, vous pouvez ajouter au début vos propres caractères valides à la variable validchars , tels que les lettres nationales qui n'existent pas dans l'alphabet anglais. C’est quelque chose que vous ne voulez peut-être pas: certains systèmes de fichiers qui ne fonctionnent pas sur UTF-8 peuvent toujours rencontrer des problèmes avec des caractères non-ASCII.

Cette fonction permet de tester la validité d'un nom de fichier unique. Elle remplacera donc les séparateurs de chemin par _, les considérant comme des caractères non valides. Si vous souhaitez ajouter cela, il est facile de modifier le si pour inclure le séparateur de chemin d'accès os.

Réponse modifiée pour python 3.6

validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
def removeDisallowedFilenameChars(filename):
    cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore')
    return ''.join(chr(c) for c in cleanedFilename if chr(c) in validFilenameChars)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top