Utilisez numbered_filename('sample-*.xml')
Python n'a pas de routine pour trouver le prochain nom de fichier dans une séquence numérotée, j'ai donc écrit un module simple (voir ci-dessous). L'utilisation est la suivante :
from numbered_filename import numbered_filename
fn = numbered_filename('sample-*.xml')
fh = open(fn, 'w')
rs = [blockresult]
fh.writelines(rs)
fh.close()
La première fois que le code est exécuté, la sortie sera dans sample-000.xml
. La prochaine exécution écrira dans sample-001.xml
, puis sample-002.xml
, et ainsi de suite. Chaque exécution ultérieure incrémente le numéro de séquence de un.
Le code du module
Enregistrez le code suivant dans un fichier appelé numbered_filename.py
.
"""Fournit une fonction pour créer des noms de fichiers incrémentés séquentiellement
basés sur un modèle simple dans lequel un astérisque est remplacé par un
nombre. Le système de fichiers est vérifié pour des fichiers existants qui correspondent
au modèle et le numéro de séquence du nom de fichier retourné est toujours un de plus
que le maximum trouvé.
"""
import glob
if __debug__:
import os
def numbered_filename(template :str ='', width :int =3) -> str:
"""Renvoie le nom de fichier suivant dans une séquence incrémentée en ajoutant un
à la plus grande numérotation actuelle dans les noms de fichiers existants.
template :str: une chaîne avec un astérisque dedans représentant où
les nombres sont placés. ('foo-*.txt').
width :int: nombre optionnel minimum de chiffres pour compléter par des zéros
la séquence. Par défaut à 3 ('000', '001', '002', ...)
Exemple d'utilisation :
from numbered_filename import numbered_filename
newfile = numbered_filename('foo-*.txt')
with open(newfile, 'w') as outfile:
outfile.write("C'est gagné !")
Étant donné un modèle de nom de fichier avec un astérisque dedans, tel que
'foo-*.txt', renvoie le même nom de fichier avec l'astérisque remplacé
par le prochain numéro dans la séquence, tel que 'foo-007.txt'. Si aucun
fichier antérieur n'existe, la numérotation commence à zéro ('foo-000.txt').
Le numéro est complété par des zéros à gauche pour contenir au moins
trois chiffres, sauf si l'argument optionnel 'width' est donné.
La complétion par des zéros peut être désactivée avec 'width=0'. Par exemple,
'numbered_filename("hackerb*", width=0)' pourrait renvoyer 'hackerb9'.
Notez que 'width' est un minimum et plus de chiffres seront utilisés si
nécessaire. (Par exemple, 'foo-1000.txt').
Peu importe le paramètre 'width', les noms de fichiers existants ne
doivent pas avoir de complétion par des zéros pour être reconnus. Par exemple,
si un répertoire contient le fichier 'foo-6.txt', le prochain nom de fichier sera
'foo-007.txt'.
Cette routine renvoie toujours un nombre plus grand après tout
fichier existant, même s'il existe un nombre plus petit. Par exemple,
dans un répertoire contenant uniquement 'foo-099.txt', le fichier suivant serait
'foo-100.txt', malgré 'foo-000' à '-098.txt' étant possibles.
Circonstances particulières : Si le modèle est une chaîne vide (''),
alors la sortie sera simplement un numéro de séquence ('007'). Si le modèle ne contient
pas d'astérisques ('foo'), alors le numéro est ajouté à la fin du nom de fichier ('foo007'). Si plus d'un
astérisque est utilisé ('*NSYNC*.txt'), alors seulement le dernier astérisque
est remplacé par un nombre ('*NSYNC007.txt'). Tous les autres astérisques
sont conservés comme '*' dans le nom de fichier.
ATTENTION : Bien que le code tente de renvoyer un nom de fichier inutilisé, il
n'est pas garanti car il y a une condition de concurrence assez évidente. Pour
l'éviter, les processus écrivant dans le même répertoire simultanément
ne doivent pas utiliser le même modèle. N'utilisez pas ceci pour créer des fichiers temporaires dans un répertoire où
un adversaire pourrait avoir un accès en écriture, comme /tmp -- utilisez plutôt 'mkstemp'.
"""
if not isinstance(template, str):
raise TypeError("numbered_filename() nécessite une chaîne en tant que modèle, comme foo-*.txt")
(filename, asterisk, extension) = template.rpartition('*')
if not asterisk:
(filename, extension) = (extension, filename)
template=f'{filename}*'
try:
files = [int(f.lstrip(filename).rstrip(extension))
for f in glob.glob(template)
if f.lstrip(filename).rstrip(extension).isdigit()]
num = sorted(files)[-1]
except (IndexError, ValueError):
num = -1
num = num + 1
spec = f'0>{width}'
numstr = format(num, spec)
if __debug__:
result = filename + numstr + extension
if os.path.exists(result):
raise AssertionError(f'Erreur : "{result}" existe déjà. Condition de course ou bug ?')
return filename + numstr + extension
Avertissement sur les conditions de course
Ce module résout le problème décrit dans la question, cependant, il ne prétend pas être sécurisé. Si votre programme crée des fichiers temporaires dans un répertoire auquel un adversaire a accès en écriture, comme /tmp, vous devriez utiliser mkstemp()
au lieu de numbered_filename().