Transformer une chaîne de caractères en un nom de fichier valide?

j'ai une chaîne de caractères 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 qu'autrement, alors disons que je veux conserver seulement des lettres, des chiffres, et un petit ensemble d'autres caractères comme "_-.() " . 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) - c'est un fichier MP3 en Ma bibliothèque avec le titre de la chanson comme nom de fichier, et est partagée et sauvegardée entre 3 machines.

223
demandé sur martineau 2008-11-17 12:02:07

20 réponses

vous pouvez regarder le Django framework pour la façon dont ils créent une" balle " à partir de texte arbitraire. Un slug Est URL - et filename - friendly.

leur template/defaultfilters.py (aux alentours de la ligne 183) définit une fonction, slugify , qui est probablement l'étalon-or pour ce genre de chose. Essentiellement, leur code est 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 en a d'autres, mais je l'ai laissé de côté, puisqu'il ne s'agit pas de limaces, mais échapper.

126
répondu S.Lott 2016-09-29 15:26:33

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 sur le formatage des fichiers ou des combinaisons de caractères valides qui sont illégaux (comme".."), par exemple, ce que vous dites permettrait un nom de fichier nommé " . txt", qui je pense n'est pas valide sur Windows. Comme il s'agit de l'approche la plus simple, j'essaierais de supprimer les espaces des caractères valid_chars et de préparer une chaîne valide connue en cas d'erreur, n'importe quelle autre approche devra savoir ce qui est permis où traiter avec limites de nommage de fichier Windows et donc être beaucoup plus complexe.

>>> import string
>>> valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
>>> valid_chars
'-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
>>> filename = "This Is a (valid) - filename%$&$ .txt"
>>> ''.join(c for c in filename if c in valid_chars)
'This Is a (valid) - filename .txt'
92
répondu Vinko Vrsalovic 2017-05-23 11:33:24

Quelle est la raison d'utiliser les chaînes de caractères comme noms de fichiers? Si la lisibilité humaine n'est pas un facteur, je choisirais 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 à gérer les collisions et il est réversible.

import base64
file_name_string = base64.urlsafe_b64encode(your_string)

mise à Jour : modification de la base de commentaire sur Matthieu.

87
répondu Igal Serban 2009-04-13 16:48:36

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'
81
répondu John Mee 2012-10-29 09:59:05

juste pour compliquer les choses, vous n'êtes pas garanti d'obtenir un nom de fichier valide juste en supprimant les caractères invalides. Comme les caractères autorisés diffèrent selon les noms de fichier, une approche conservatrice pourrait finir par transformer un nom valide en un nom invalide. Vous pouvez ajouter une manipulation spéciale pour les cas où:

  • la chaîne est tous les caractères invalides (vous laissant avec une chaîne vide)

  • vous finissez avec une corde avec un sens particulier, par exemple "." ou." ."

  • sur windows, certains noms d'appareils sont réservés. Par exemple, vous ne pouvez pas créer un fichier nommé "nul", "nul.txt" (ou nul.les noms réservés sont:

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

vous pouvez probablement contourner ces problèmes en préparant une chaîne de caractères aux noms de fichiers qui ne peut jamais aboutir à l'un de ces cas, et en enlevant les caractères invalides.

33
répondu Brian 2008-11-17 09:57:40

il y a un beau projet sur Github appelé python-slugify :

Installation:

pip install python-slugify

ensuite utiliser:

>>> from slugify import slugify
>>> txt = "This\ is/ a%#$ test ---"
>>> slugify(txt)
'this-is-a-test'
20
répondu Shoham 2015-04-29 11:19:47

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)

La unicodedata.normalize call remplace les caractères accentués par l'équivalent non accentué, ce qui est mieux que de simplement les enlever. Après cela, tous les caractères interdits sont supprimés.

ma solution ne prévient pas une chaîne de caractères connue pour éviter d'éventuels noms de fichiers rejetés, parce que je sais qu'ils ne peuvent pas se produire compte tenu de mon format de nom de fichier particulier. Une solution plus générale besoin de le faire.

17
répondu Sophie Gage 2009-03-30 19:40:17

gardez à l'esprit, il n'y a en fait aucune restriction sur les noms de fichiers sur les systèmes Unix autre que

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

tout le reste est 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\n3[31m\ red\ 3[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 je les ai fait entrer en vigueur.

pour le divertissement, mettez un caractère BEL dans un nom de répertoire et regarder le plaisir qui en découle lorsque vous CD ;)

13
répondu Kent Fredric 2008-11-17 10:45:54

tout comme S. Lott répondu, Vous pouvez regarder le Django Framework pour la façon dont ils convertissent une chaîne de caractères en un nom de fichier valide.

la version la plus récente et mise à jour est disponible en utils/text.py, et définit "get_valid_filename", 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/master/django/utils/text.py )

10
répondu cowlinator 2017-10-18 00:24:44

vous pouvez utiliser le re.sub() méthode pour remplacer ce qui n'est pas "filelike". Mais en effet, chaque caractère pourrait être valide; donc il n'y a pas de fonctions prébuilt (je crois), pour le faire.

import re

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

se traduirait par un filehandle to /tmp/filename.txt.

7
répondu gx. 2015-07-01 09:35:10
>>> 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.).

7
répondu jfs 2017-06-22 11:26:50

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

cela semble comme beaucoup moins de travail et est valable n'importe quel OS que vous utilisez.

6
répondu James Anderson 2012-05-30 01:46:53

sur une ligne:

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

vous pouvez également mettre le caractère ' _ 'pour le rendre plus lisible (dans le cas de remplacer les slashs, par exemple)

6
répondu mnach 2016-08-04 11:29:03

Bien que vous devez être prudent. Ce n'est pas clairement dit dans votre intro, si vous ne regardez que la langue latine. Certains mots peuvent devenir insignifiants ou une autre signification si vous les épurez avec des caractères ascii seulement.

imaginez que vous avez "forêt poésie", votre assainissement pourrait donner " fort-posie "(fort + quelque chose de dénué de sens)

pire si vous avez affaire à des caractères chinois.

" système pourrait finir par faire "- - - " qui est condamné à l'échec après un certain temps et pas très utile. Donc, si vous traitez uniquement les fichiers je les encourage à appeler un générique de chaîne de contrôle ou de garder les personnages comme il est. Pour URIs, à peu près la même chose.

5
répondu karlcow 2009-03-11 10:44:46

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 retrouver avec une chaîne vide à partir de décapage trop de caractères.

avec les noms de Fichiers réservés De Windows et les problèmes avec les points, la réponse la plus sûre à la question "Comment puis-je normaliser un nom de fichier valide à partir d'une entrée d'utilisateur arbitraire?"est "même pas la peine d'essayer": si vous pouvez trouver tout autre moyen de les éviter (par exemple. utilisant entier primaire clés à partir d'une base de données comme les noms de fichiers), le faire.

si vous DEVEZ, et vous avez vraiment besoin d'autoriser les espaces et ‘."pour les extensions de fichier faisant partie du nom, essayez quelque chose comme:

import re
badchars= re.compile(r'[^A-Za-z0-9_. ]+|^\.|\.$|^ | $|^$')
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 droit, surtout sur les OSs inattendus - par exemple RISC OS déteste les espaces et les utilisations"."comme séparateur de répertoire.

4
répondu bobince 2008-11-17 13:24:19

la plupart de ces solutions ne fonctionnent pas.

'/bonjour/monde' -> 'helloworld'

'/helloworld' / - > 'helloworld'

ce n'est pas ce que vous voulez en général, disons que vous sauvegardez le html pour chaque lien, vous allez écraser le html pour une page Web différente.

Je pickle un dict tel que:

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

2 représente le nombre qui doit être ajouté au nom de fichier suivant.

je regarde le nom du fichier à chaque fois à partir du dict. Si elle n'est pas là, j'en crée une nouvelle, en ajoutant le numéro max si nécessaire.

2
répondu robert king 2012-05-16 01:04:34

j'ai aimé l'approche de python-slugify ici, mais il était en train d'enlever des points aussi loin qui n'a pas été désiré. Donc je l'ai optimisé pour télécharger un nom de fichier propre sur s3 de cette façon:

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 infaillible, il fonctionne avec des noms de fichiers sans extension et il fonctionne même pour les noms de fichiers de caractères dangereux (résultat est none ici).

2
répondu therealmarv 2017-10-05 16:51:07

pas exactement ce que L'OP demandait mais c'est ce que j'utilise car 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+-_ .'))

résultat est" quelque peu " lisible, au moins d'un point de vue sysadmin.

1
répondu makeroo 2014-09-12 12:19:39

je suis sûr que ce n'est pas une bonne réponse, car il modifie la chaîne qu'il est en boucle, mais il semble bien fonctionner:

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, '')
0
répondu TankorSmash 2012-05-05 03:56:00

mise à JOUR

tous les liens brisés au-delà de réparation dans cette réponse de 6 ans.

aussi, je ne le ferais plus de cette façon, juste base64 encoder ou laisser tomber des chars dangereux. Python 3 Exemple:

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, de sorte que vous pouvez récupérer le nom de fichier original à nouveau.

mais selon le cas d'utilisation vous pourriez être mieux générer un nom de fichier aléatoire et stocker les métadonnées dans un fichier séparé ou DB.

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'

ORIGINAL LINKROTTEN ANSWER :

le projet bobcat contient un module python qui fait cela.

ce n'est pas complètement robuste, voir ce post et ce réponse .

donc, comme noté: base64 encodage est probablement un meilleur idée si la lisibilité n'a pas d'importance.

0
répondu wires 2015-12-28 14:30:16