¿Cómo modificar un archivo de texto?

175

Estoy usando Python, y me gustaría insertar una cadena en un archivo de texto sin eliminar o copiar el archivo. ¿Cómo puedo hacer eso?

Martineau
fuente
1
Puede referirse a esta respuesta de Alex Martelli.
Alok
@Ani la otra publicación es un duplicado de Insertar línea en la posición especificada de un archivo de texto de todos modos y ciertamente hay respuestas compuestas claras aquí, ¿por qué no agregar su respuesta aquí en lugar de la otra manera? La respuesta aceptada no es un requisito para una buena pregunta.
Bhargav Rao
@BhargavRao Vote se retractó. ¡Aunque debería haber encontrado ese duplicado!
Ani Menon

Respuestas:

134

Lamentablemente, no hay forma de insertar en el medio de un archivo sin volver a escribirlo. Como lo han indicado los pósters anteriores, puede agregar un archivo o sobrescribir parte de él mediante la búsqueda, pero si desea agregar cosas al principio o en el medio, tendrá que volver a escribirlo.

Esto es una cosa del sistema operativo, no una cosa de Python. Es lo mismo en todos los idiomas.

Lo que suelo hacer es leer el archivo, hacer las modificaciones y escribirlo en un nuevo archivo llamado myfile.txt.tmp o algo así. Esto es mejor que leer todo el archivo en la memoria porque el archivo puede ser demasiado grande para eso. Una vez que se completa el archivo temporal, lo renombro igual que el archivo original.

Esta es una forma buena y segura de hacerlo porque, si la escritura del archivo se bloquea o cancela por cualquier motivo, aún tiene su archivo original intacto.

Adam Pierce
fuente
3
¿Las herramientas de Unix como awk / sed hacen algo similar en su código?
Manish Gill
No es cierto que esto sea igual en todos los idiomas. En ActionScript: fileStream.openAsync (nombre de archivo, FileMode.UPDATE); Luego puedo ir a cualquier parte del archivo que desee y cambiar cualquier cosa.
AndrewBenjamin
2
@ AndrewBenjamin ¿Sabes qué llamadas al sistema está haciendo ActionScript? ¿Existe la posibilidad de que openAsync lea el archivo y escriba uno nuevo después de la llamada?
AlexLordThorsen
@Rawrgulmuffins no lo hago. Sin embargo, sé que no está leyendo todo el archivo en la memoria, ya que lo he usado para manejar tamaños de archivos de varios GB. Sospecho que es lo mismo que escribir con C # streamwriter. Veo Python como una herramienta para hacer cosas pequeñas rápidamente, en lugar de desarrollo a gran escala y manipulación de archivos.
AndrewBenjamin
44
@AndrewBenjamin, el usuario no pregunta acerca de buscar en el archivo y cambiarlo (cada idioma que conozco puede hacerlo); pregunta sobre insertar texto, que es diferente a simplemente cambiar / sobrescribir lo que ya está en el archivo. Quizás en la aplicación práctica es diferente, pero nada de lo que puedo encontrar en la API de ActionScript indica que se comporta de manera diferente a cualquier otro lenguaje a este respecto.
eestrada
104

Depende de lo que quieras hacer. Para agregar puede abrirlo con "a":

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

Si desea pretender algo, primero debe leer el archivo:

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
Armin Ronacher
fuente
9
Solo una pequeña adición, para usar la withdeclaración en Python 2.5, necesita agregar "from future import with_statement". Aparte de eso, abrir archivos con la withdeclaración es definitivamente más legible y menos propenso a errores que el cierre manual.
Alexander Kojevnikov
2
Puede considerar que la fileinputlib de ayuda con maneja la rutina de abrir / leer / modificar / escribir / reemplazar sucia muy bien al usar el inline=Truearg. Ejemplo aquí: stackoverflow.com/a/2363893/47390
mikegreenberg
3
Simplemente no olvides cerrar el archivo. f.Close()
D.Rosado
55
No es un estilo que uso, D.Rosado, pero cuando uso el estilo with, no creo que necesite cerrarlo manualmente. El con realiza un seguimiento del recurso que crea.
Chris
44
Usted no necesita manualmente cerrar el archivo. Ese es el punto de usar "con" aquí. (Bueno, en realidad, Python hace esto tan pronto como el objeto del archivo se recolecta basura, lo que en CPython ocurre cuando el nombre vinculado a él queda fuera de alcance ... pero otras implementaciones no lo hacen, y CPython podría dejar de hacerlo algún día , así que se recomienda "con")
Jürgen A. Erhard
71

El fileinputmódulo de la biblioteca estándar de Python reescribirá un archivo in situ si utiliza el parámetro 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
Dave
fuente
1
¿Cómo se espera que esto funcione en python3? Acabo de portar una aplicación que tenía un código como este de python a python3 y simplemente no pude hacer que esto funcionara en absoluto. La variable 'línea' es un tipo de bytes, intenté decodificarla en Unicode y luego modificarla y luego codificarla nuevamente en bytes, pero simplemente no funcionaría correctamente. Planteó alguna excepción que no puedo recordar de mi cabeza. ¿Las personas que usan fileinput inplace = 1 en python3 tienen algún éxito?
robru
1
@Robru: aquí está el código Python 3
jfs
13
Pero no hay problema porque lo probó primero en un archivo sin importancia, ¿verdad?
Paula Livingstone
33

La reescritura de un archivo en su lugar a menudo se realiza guardando la copia anterior con un nombre modificado. La gente de Unix agrega una ~para marcar la anterior. La gente de Windows hace todo tipo de cosas: agrega .bak o .old, o cambia el nombre del archivo por completo o coloca el ~ al frente del nombre.

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()

En lugar de shutil, puede usar lo siguiente.

import os
os.rename( aFile, aFile+"~" )
S.Lott
fuente
1
Se ve bien. ¿Se pregunta si .readlines () es mejor que iterar la fuente?
bozdoz
2
@bozdoz: iterar es mejor ya que readlines lee todo el archivo. No es bueno para archivos grandes. Por supuesto, esto supone que puede hacer sus modificaciones de forma tan localizada. A veces no puedes, o tu código se vuelve mucho más complicado.
Jürgen A. Erhard
@ S.Lott: os.rename(aFile, aFile + "~")modificará el nombre del archivo fuente, no creará una copia.
Patapoom
14

El módulo mmap de Python le permitirá insertar en un archivo. El siguiente ejemplo muestra cómo se puede hacer en Unix (Windows mmap puede ser diferente). Tenga en cuenta que esto no maneja todas las condiciones de error y puede corromper o perder el archivo original. Además, esto no manejará cadenas 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()

También es posible hacer esto sin mmap con archivos abiertos en modo 'r +', pero es menos conveniente y menos eficiente ya que tendría que leer y almacenar temporalmente el contenido del archivo desde la posición de inserción a EOF, lo que podría ser enorme

mhawke
fuente
14

Como lo mencionó Adam, debe tener en cuenta las limitaciones de su sistema antes de poder decidir si tiene suficiente memoria para leerlo todo en la memoria, reemplazar partes y volver a escribirlo.

Si está tratando con un archivo pequeño o no tiene problemas de memoria, esto podría ayudar:

Opción 1) Lea todo el archivo en la memoria, realice una sustitución de expresiones regulares en toda o parte de la línea y reemplácela con esa línea más la línea adicional. Deberá asegurarse de que la "línea media" sea única en el archivo o, si tiene marcas de tiempo en cada línea, esto debería ser bastante confiable.

# 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()

Opción 2) Calcule la línea media y reemplácela con esa línea más la línea extra.

# 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()
Maxime R.
fuente
2

Escribió una pequeña clase para hacer esto limpiamente.

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()

Entonces puedes usarlo de esta manera:

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
ananth krishnan
fuente
Esto no funciona para mí personalmente, agrega texto al archivo, ¡pero elimina todo primero!
Bret Hawker el
De hecho, esto no funciona en absoluto. Es una pena, porque parecía una buena idea.
Mario Krušelj
0

Si conoce unix, puede probar lo siguiente:

Notas: $ significa el símbolo del sistema

Digamos que tiene un archivo my_data.txt con contenido como tal:

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

Luego, usando el osmódulo, puede usar los sedcomandos habituales

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 no eres consciente de sed, échale un vistazo, es extremadamente útil.

G. LC
fuente
3
No es Pythonic en absoluto
DarkSuniuM