Comment modifier un fichier texte?


175

J'utilise Python et je souhaite insérer une chaîne dans un fichier texte sans supprimer ni copier le fichier. Comment puis je faire ça?


1
Vous pouvez vous référer à cette réponse d'Alex Martelli.
Alok



@Ani l'autre message est de toute façon une copie de Insertion de ligne à la position spécifiée d'un fichier texte et il y a certainement des réponses claires et composées ici, pourquoi ne pas ajouter votre réponse ici au lieu de l'inverse? Une réponse acceptée n'est pas une exigence pour une bonne question.
Bhargav Rao

@BhargavRao Vote rétracté. J'aurais dû trouver ce duplicata!
Ani Menon

Réponses:


134

Malheureusement, il n'y a aucun moyen d'insérer au milieu d'un fichier sans le réécrire. Comme les affiches précédentes l'ont indiqué, vous pouvez ajouter à un fichier ou en écraser une partie en utilisant la recherche, mais si vous voulez ajouter des éléments au début ou au milieu, vous devrez le réécrire.

C'est une chose du système d'exploitation, pas une chose de Python. C'est la même chose dans toutes les langues.

Ce que je fais habituellement, c'est lire à partir du fichier, apporter les modifications et l'écrire dans un nouveau fichier appelé myfile.txt.tmp ou quelque chose comme ça. C'est mieux que de lire le fichier entier en mémoire car le fichier peut être trop volumineux pour cela. Une fois le fichier temporaire terminé, je le renomme de la même manière que le fichier d'origine.

C'est un bon moyen sûr de le faire, car si l'écriture du fichier se bloque ou s'interrompt pour une raison quelconque, vous avez toujours votre fichier d'origine intact.


3
Les outils Unix comme awk / sed font-ils quelque chose de similaire dans leur code?
Manish Gill

Ce n'est pas vrai que ce soit la même chose dans toutes les langues. Dans ActionScript: fileStream.openAsync (nom de fichier, FileMode.UPDATE); Ensuite, je peux aller n'importe où dans le fichier que je veux et changer quoi que ce soit.
AndrewBenjamin du

2
@AndrewBenjamin Savez-vous quel système appelle ActionScript? Y a-t-il une possibilité qu'openAsync lise le fichier et en écrive un nouveau après l'appel?
AlexLordThorsen

@Rawrgulmuffins, je ne le fais pas. Cependant, je sais qu'il ne lit pas le fichier entier en mémoire, car je l'ai utilisé pour gérer des tailles de fichiers de plusieurs Go. Je soupçonne que c'est la même chose que d'écrire avec C # streamwriter. Je considère python comme un outil pour faire de petites choses rapidement, plutôt que comme un développement à grande échelle et une manipulation de fichiers.
AndrewBenjamin

4
@AndrewBenjamin, l'utilisateur ne demande pas de chercher dans le fichier et de le modifier (toutes les langues que je connais peuvent le faire); il demande comment insérer du texte, ce qui est différent du simple changement / écrasement de ce qui se trouve déjà dans le fichier. Peut-être que dans l'application pratique, il est différent, mais rien que je puisse trouver dans l' API ActionScript n'indique qu'il se comporte différemment de tout autre langage à cet égard.
eestrada

104

Tout dépends de ce que tu veux faire. Pour ajouter, vous pouvez l'ouvrir avec "a":

 with open("foo.txt", "a") as f:
     f.write("new line\n")

Si vous voulez préprendre quelque chose, vous devez d'abord lire le fichier:

with open("foo.txt", "r+") as f:
     old = f.read() # read everything in the file
     f.seek(0) # rewind
     f.write("new line\n" + old) # write the new line before

9
Juste un petit ajout, pour utiliser l' withinstruction dans Python 2.5, vous devez ajouter "from future import with_statement". En dehors de cela, l'ouverture de fichiers avec l' withinstruction est certainement plus lisible et moins sujette aux erreurs que la fermeture manuelle.
Alexander Kojevnikov

2
Vous pouvez considérer la fileinputbibliothèque d'aide avec des manipulations de la routine dirty open / read / modify / write / replace bien lorsque vous utilisez inline=Truearg. Exemple ici: stackoverflow.com/a/2363893/47390
mikegreenberg

3
N'oubliez pas de fermer le fichier. f.Close()
D.Rosado

5
Ce n'est pas un style que j'utilise, D.Rosado, mais lorsque vous utilisez le style with, je ne pense pas que vous ayez besoin de fermer manuellement. Le avec garde une trace de la ressource qu'il crée.
Chris

4
Vous n'avez pas besoin de fermer manuellement le fichier. C'est tout l'intérêt d'utiliser "avec" ici. (Eh bien, en fait, Python le fait dès que l'objet fichier est ramassé, ce qui dans CPython se produit lorsque le nom qui lui est lié sort de la portée ... mais d'autres implémentations ne le font pas, et CPython pourrait arrêter de le faire un jour , donc "avec" est recommandé)
Jürgen A. Erhard

71

Le fileinputmodule de la bibliothèque standard Python réécrira un fichier en place si vous utilisez le paramètre inplace = 1:

import sys
import fileinput

# replace all occurrences of 'sit' with 'SIT' and insert a line after the 5th
for i, line in enumerate(fileinput.input('lorem_ipsum.txt', inplace=1)):
    sys.stdout.write(line.replace('sit', 'SIT'))  # replace 'sit' and write
    if i == 4: sys.stdout.write('\n')  # write a blank line after the 5th line

1
Comment cela devrait-il fonctionner en python3? Je viens de porter une application qui contenait du code comme celui-ci de python à python3 et je ne pouvais tout simplement pas le faire fonctionner correctement. La variable «ligne» est un type d'octets, j'ai essayé de la décoder en unicode, puis de la modifier, puis de la coder en octets, mais cela ne fonctionnerait tout simplement pas correctement. Cela a soulevé une exception dont je ne me souviens pas du haut de ma tête. Les gens utilisent-ils fileinput inplace = 1 en python3 avec un succès?
robru

1
@Robru: voici le code Python 3
jfs

13
Mais ce n'est pas un problème car vous l'avez testé d'abord sur un fichier sans importance?
Paula Livingstone

33

La réécriture d'un fichier sur place se fait souvent en enregistrant l'ancienne copie avec un nom modifié. Les gens d'Unix ajoutent un ~pour marquer l'ancien. Les gens de Windows font toutes sortes de choses - ajoutez .bak ou .old - ou renommez le fichier entièrement ou mettez le ~ au début du nom.

import shutil
shutil.move( afile, afile+"~" )

destination= open( aFile, "w" )
source= open( aFile+"~", "r" )
for line in source:
    destination.write( line )
    if <some condition>:
        destination.write( >some additional line> + "\n" )
source.close()
destination.close()

Au lieu de shutil, vous pouvez utiliser ce qui suit.

import os
os.rename( aFile, aFile+"~" )

1
Cela semble bon. Vous vous demandez si .readlines () vaut mieux que d'itérer la source?
bozdoz

2
@bozdoz: il est préférable d'itérer puisque readlines lit tout le fichier. Pas bon pour les gros fichiers. Bien entendu, cela suppose que vous puissiez effectuer vos modifications de manière aussi localisée. Parfois, vous ne pouvez pas, ou votre code devient beaucoup plus compliqué.
Jürgen A. Erhard

@ S.Lott: os.rename(aFile, aFile + "~")modifiera le nom du fichier source, sans créer de copie.
Patapoom

14

Le module mmap de Python vous permettra d'insérer dans un fichier. L'exemple suivant montre comment cela peut être fait sous Unix (Windows mmap peut être différent). Notez que cela ne gère pas toutes les conditions d'erreur et que vous risquez de corrompre ou de perdre le fichier d'origine. De plus, cela ne gérera pas les chaînes Unicode.

import os
from mmap import mmap

def insert(filename, str, pos):
    if len(str) < 1:
        # nothing to insert
        return

    f = open(filename, 'r+')
    m = mmap(f.fileno(), os.path.getsize(filename))
    origSize = m.size()

    # or this could be an error
    if pos > origSize:
        pos = origSize
    elif pos < 0:
        pos = 0

    m.resize(origSize + len(str))
    m[pos+len(str):] = m[pos:origSize]
    m[pos:pos+len(str)] = str
    m.close()
    f.close()

Il est également possible de le faire sans mmap avec des fichiers ouverts en mode `` r + '', mais c'est moins pratique et moins efficace car vous auriez à lire et à stocker temporairement le contenu du fichier de la position d'insertion à EOF - ce qui pourrait être énorme.


14

Comme mentionné par Adam, vous devez prendre en compte les limitations de votre système avant de pouvoir décider si vous avez suffisamment de mémoire pour tout lire en mémoire, remplacez-en des parties et réécrivez-le.

Si vous avez affaire à un petit fichier ou que vous n'avez pas de problèmes de mémoire, cela peut aider:

Option 1) Lire le fichier entier en mémoire, faire une substitution regex sur tout ou partie de la ligne et le remplacer par cette ligne plus la ligne supplémentaire. Vous devrez vous assurer que la «ligne médiane» est unique dans le fichier ou si vous avez des horodatages sur chaque ligne, cela devrait être assez fiable.

# open file with r+b (allow write and binary mode)
f = open("file.log", 'r+b')   
# read entire content of file into memory
f_content = f.read()
# basically match middle line and replace it with itself and the extra line
f_content = re.sub(r'(middle line)', r'\1\nnew line', f_content)
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(f_content)
# close file
f.close()

Option 2) Calculez la ligne médiane et remplacez-la par cette ligne plus la ligne supplémentaire.

# open file with r+b (allow write and binary mode)
f = open("file.log" , 'r+b')   
# get array of lines
f_content = f.readlines()
# get middle line
middle_line = len(f_content)/2
# overwrite middle line
f_content[middle_line] += "\nnew line"
# return pointer to top of file so we can re-write the content with replaced string
f.seek(0)
# clear file content 
f.truncate()
# re-write the content with the updated content
f.write(''.join(f_content))
# close file
f.close()

2

A écrit une petite classe pour faire ça proprement.

import tempfile

class FileModifierError(Exception):
    pass

class FileModifier(object):

    def __init__(self, fname):
        self.__write_dict = {}
        self.__filename = fname
        self.__tempfile = tempfile.TemporaryFile()
        with open(fname, 'rb') as fp:
            for line in fp:
                self.__tempfile.write(line)
        self.__tempfile.seek(0)

    def write(self, s, line_number = 'END'):
        if line_number != 'END' and not isinstance(line_number, (int, float)):
            raise FileModifierError("Line number %s is not a valid number" % line_number)
        try:
            self.__write_dict[line_number].append(s)
        except KeyError:
            self.__write_dict[line_number] = [s]

    def writeline(self, s, line_number = 'END'):
        self.write('%s\n' % s, line_number)

    def writelines(self, s, line_number = 'END'):
        for ln in s:
            self.writeline(s, line_number)

    def __popline(self, index, fp):
        try:
            ilines = self.__write_dict.pop(index)
            for line in ilines:
                fp.write(line)
        except KeyError:
            pass

    def close(self):
        self.__exit__(None, None, None)

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        with open(self.__filename,'w') as fp:
            for index, line in enumerate(self.__tempfile.readlines()):
                self.__popline(index, fp)
                fp.write(line)
            for index in sorted(self.__write_dict):
                for line in self.__write_dict[index]:
                    fp.write(line)
        self.__tempfile.close()

Ensuite, vous pouvez l'utiliser de cette façon:

with FileModifier(filename) as fp:
    fp.writeline("String 1", 0)
    fp.writeline("String 2", 20)
    fp.writeline("String 3")  # To write at the end of the file

Cela ne fonctionne pas pour moi personnellement, cela ajoute du texte au fichier mais cela supprime tout d'abord!
Bret Hawker

En effet, cela ne fonctionne pas du tout. Dommage, car cela semblait être une bonne idée.
Mario Krušelj le

0

Si vous connaissez unix, vous pouvez essayer ce qui suit:

Notes: $ signifie l'invite de commande

Supposons que vous ayez un fichier my_data.txt avec un contenu en tant que tel:

$ cat my_data.txt
This is a data file
with all of my data in it.

Ensuite, en utilisant le osmodule, vous pouvez utiliser les sedcommandes habituelles

import os

# Identifiers used are:
my_data_file = "my_data.txt"
command = "sed -i 's/all/none/' my_data.txt"

# Execute the command
os.system(command)

Si vous n'êtes pas au courant de sed, vérifiez-le, il est extrêmement utile.


3
Ce n'est pas du tout
pythonique
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.