Python concaténer des fichiers texte

168

J'ai une liste de 20 noms de fichiers, comme ['file1.txt', 'file2.txt', ...]. Je souhaite écrire un script Python pour concaténer ces fichiers dans un nouveau fichier. Je pourrais ouvrir chaque fichier f = open(...), lire ligne par ligne en appelant f.readline()et écrire chaque ligne dans ce nouveau fichier. Cela ne me paraît pas très "élégant", surtout la partie où je dois lire // écrire ligne par ligne.

Existe-t-il une manière plus "élégante" de faire cela en Python?

JJ Beck
la source
7
Ce n'est pas python, mais dans le script shell, vous pouvez faire quelque chose comme cat file1.txt file2.txt file3.txt ... > output.txt. En python, si vous n'aimez pas readline(), il y en a toujours readlines()ou simplement read().
jedwards
1
@jedwards lance simplement la cat file1.txt file2.txt file3.txtcommande à l'aide du subprocessmodule et vous avez terminé. Mais je ne sais pas si catfonctionne dans Windows.
Ashwini Chaudhary
5
En guise de note, la façon dont vous décrivez est une manière terrible de lire un fichier. Utilisez l' withinstruction pour vous assurer que vos fichiers sont correctement fermés et parcourez le fichier pour obtenir des lignes, plutôt que d'utiliser f.readline().
Gareth Latty
@jedwards cat ne fonctionne pas lorsque le fichier texte est unicode.
Avi Cohen
Analyse réelle waymoot.org/home/python_string
nu everest

Réponses:

260

Cela devrait le faire

Pour les gros fichiers:

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for fname in filenames:
        with open(fname) as infile:
            for line in infile:
                outfile.write(line)

Pour les petits fichiers:

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for fname in filenames:
        with open(fname) as infile:
            outfile.write(infile.read())

… Et un autre intéressant auquel j'ai pensé :

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for line in itertools.chain.from_iterable(itertools.imap(open, filnames)):
        outfile.write(line)

Malheureusement, cette dernière méthode laisse quelques descripteurs de fichiers ouverts, dont le GC devrait de toute façon s'occuper. J'ai juste pensé que c'était intéressant

inspecteurG4dget
la source
9
Ce sera, pour les fichiers volumineux, une mémoire très inefficace.
Gareth Latty
1
@ inspectorG4dget: Je ne vous le demandais pas, je demandais à eyquem, qui se plaignait que votre solution n'allait pas être efficace. Je suis prêt à parier que c'est plus que suffisamment efficace pour le cas d'utilisation de l'OP, et quel que soit le cas d'utilisation qu'eyquem a à l'esprit. S'il pense que ce n'est pas le cas, c'est sa responsabilité de le prouver avant de vous demander de l'optimiser.
abarnert
2
que considérons-nous comme un gros fichier?
Dee
4
@dee: un fichier si volumineux que son contenu ne rentre pas dans la mémoire principale
inspectorG4dget
7
Juste pour répéter: c'est la mauvaise réponse, shutil.copyfileobj est la bonne réponse.
Paul Crowley
193

Utilisez shutil.copyfileobj.

Il lit automatiquement les fichiers d'entrée bloc par bloc pour vous, ce qui est plus efficace et lit les fichiers d'entrée et fonctionnera même si certains des fichiers d'entrée sont trop volumineux pour tenir en mémoire:

import shutil

with open('output_file.txt','wb') as wfd:
    for f in ['seg1.txt','seg2.txt','seg3.txt']:
        with open(f,'rb') as fd:
            shutil.copyfileobj(fd, wfd)
Miaou
la source
2
for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'):Eh bien, j'ai remplacé l'instruction for pour inclure tous les fichiers dans le répertoire, mais j'ai output_filecommencé à devenir vraiment énorme comme dans des centaines de Go en un temps très rapide.
R__raki__
10
Notez que cela fusionnera les dernières chaînes de chaque fichier avec les premières chaînes du fichier suivant s'il n'y a pas de caractères EOL. Dans mon cas, j'ai obtenu un résultat totalement corrompu après avoir utilisé ce code. J'ai ajouté wfd.write (b "\ n") après copyfileobj pour obtenir un résultat normal
Thelambofgoat
1
@Thelambofgoat Je dirais que ce n'est pas une pure concaténation dans ce cas, mais bon, tout ce qui convient à vos besoins.
HelloGoodbye
59

C'est exactement à quoi sert fileinput :

import fileinput
with open(outfilename, 'w') as fout, fileinput.input(filenames) as fin:
    for line in fin:
        fout.write(line)

Pour ce cas d'utilisation, ce n'est vraiment pas beaucoup plus simple que de simplement itérer manuellement sur les fichiers, mais dans d'autres cas, avoir un seul itérateur qui itère sur tous les fichiers comme s'il s'agissait d'un seul fichier est très pratique. (De plus, le fait de fileinputfermer chaque fichier dès que c'est fait signifie qu'il n'y a pas besoin de withou closechacun d'eux, mais ce n'est qu'une économie d'une ligne, pas si grave.)

Il existe d'autres fonctionnalités intéressantes fileinput, comme la possibilité de modifier sur place des fichiers simplement en filtrant chaque ligne.


Comme indiqué dans les commentaires, et discuté dans un autre article , fileinputpour Python 2.7 ne fonctionnera pas comme indiqué. Voici une légère modification pour rendre le code compatible Python 2.7

with open('outfilename', 'w') as fout:
    fin = fileinput.input(filenames)
    for line in fin:
        fout.write(line)
    fin.close()
Abarnert
la source
@Lattyware: Je pense que la plupart des gens qui apprennent ce sujet se fileinputfont dire que c'est un moyen de transformer un simple sys.argv(ou ce qui reste comme argument après optparse/ etc.) En un gros fichier virtuel pour des scripts triviaux, et ne pense pas à l'utiliser pour quoi que ce soit else (c'est-à-dire lorsque la liste n'est pas des arguments de ligne de commande). Ou ils apprennent, mais oublient ensuite - je continue à le redécouvrir chaque année ou deux…
abarnert
1
@abament Je pense que ce for line in fileinput.input()n'est pas la meilleure façon de choisir dans ce cas particulier: l'OP veut concaténer des fichiers, pas les lire ligne par ligne, ce qui est un processus théoriquement plus long à exécuter
eyquem
1
@eyquem: Ce n'est pas un processus plus long à exécuter. Comme vous l'avez souligné vous-même, les solutions basées sur des lignes ne lisent pas un caractère à la fois; ils lisent en morceaux et extraient des lignes d'un tampon. Le temps d'E / S submergera complètement le temps d'analyse de ligne, donc tant que l'implémenteur n'a pas fait quelque chose d'horriblement stupide dans la mise en mémoire tampon, ce sera tout aussi rapide (et peut-être même plus rapide que d'essayer de deviner un bon tampon taille vous-même, si vous pensez que 10000 est un bon choix).
abarnert
1
@abarnert NO, 10000 n'est pas un bon choix. C'est en effet un très mauvais choix car ce n'est pas une puissance de 2 et c'est ridiculement une petite taille. De meilleures tailles seraient 2097152 (2 21), 16777216 (2 24) ou même 134217728 (2 ** 27), pourquoi pas?, 128 Mo, ce n'est rien dans une RAM de 4 Go.
eyquem
2
Exemple de code pas tout à fait valide pour Python 2.7.10 et versions ultérieures: stackoverflow.com/questions/30835090
...
8

Je ne connais pas l'élégance, mais cela fonctionne:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")
Daniel
la source
8
vous pouvez même éviter la boucle: import os; os.system ("fichier cat * .txt >> OutFile.txt")
lib
6
pas de plate-forme croisée et se cassera pour les noms de fichiers avec des espaces
mouton volant
3
Ceci n'est pas sûr; aussi, catpeut prendre une liste de fichiers, donc pas besoin de l'appeler à plusieurs reprises. Vous pouvez facilement le sécuriser en appelant à la subprocess.check_callplace deos.system
Clément
5

Quel est le problème avec les commandes UNIX? (étant donné que vous ne travaillez pas sous Windows):

ls | xargs cat | tee output.txt fait le travail (vous pouvez l'appeler depuis python avec un sous-processus si vous le souhaitez)

lucasg
la source
21
car c'est une question sur python.
ObscureRobot
2
Rien de mal en général, mais cette réponse est erronée (ne passez pas la sortie de ls à xargs, transmettez simplement la liste des fichiers à cat directement :) cat * | tee output.txt.
Clément
S'il peut également insérer un nom de fichier, ce serait génial.
Deqing
@Deqing Pour spécifier les noms de fichiers d'entrée, vous pouvez utilisercat file1.txt file2.txt | tee output.txt
GoTrained
1
... et vous pouvez désactiver l'envoi vers stdout (impression dans le terminal) en ajoutant 1> /dev/nullà la fin de la commande
GoTrained
4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Un simple benchmark montre que le shutil fonctionne mieux.

haoming
la source
3

Une alternative à la réponse @ inspectorG4dget (meilleure réponse à ce jour 29-03-2016). J'ai testé avec 3 fichiers de 436 Mo.

@ solution inspectorG4dget: 162 secondes

La solution suivante: 125 secondes

from subprocess import Popen
filenames = ['file1.txt', 'file2.txt', 'file3.txt']
fbatch = open('batch.bat','w')
str ="type "
for f in filenames:
    str+= f + " "
fbatch.write(str + " > file4results.txt")
fbatch.close()
p = Popen("batch.bat", cwd=r"Drive:\Path\to\folder")
stdout, stderr = p.communicate()

L'idée est de créer un fichier batch et de l'exécuter en tirant parti de la «vieille bonne technologie». Son semi-python mais fonctionne plus vite. Fonctionne pour Windows.

João Palma
la source
3

Si vous avez beaucoup de fichiers dans le répertoire, il glob2peut être préférable de générer une liste de noms de fichiers plutôt que de les écrire à la main.

import glob2

filenames = glob2.glob('*.txt')  # list of all .txt files in the directory

with open('outfile.txt', 'w') as f:
    for file in filenames:
        with open(file) as infile:
            f.write(infile.read()+'\n')
Sharad
la source
2

Découvrez la méthode .read () de l'objet File:

http://docs.python.org/2/tutorial/inputoutput.html#methods-of-file-objects

Vous pouvez faire quelque chose comme:

concat = ""
for file in files:
    concat += open(file).read()

ou une façon python plus `` élégante '':

concat = ''.join([open(f).read() for f in files])

qui, selon cet article: http://www.skymind.com/~ocrow/python_string/ serait également le plus rapide.

Alex Kawrykow
la source
10
Cela produira une chaîne géante qui, selon la taille des fichiers, pourrait être plus grande que la mémoire disponible. Comme Python fournit un accès paresseux facile aux fichiers, c'est une mauvaise idée.
Gareth Latty
2

Si les fichiers ne sont pas gigantesques:

with open('newfile.txt','wb') as newf:
    for filename in list_of_files:
        with open(filename,'rb') as hf:
            newf.write(hf.read())
            # newf.write('\n\n\n')   if you want to introduce
            # some blank lines between the contents of the copied files

Si les fichiers sont trop gros pour être entièrement lus et conservés en RAM, l'algorithme doit être un peu différent pour lire chaque fichier à copier en boucle par morceaux de longueur fixe, en utilisant read(10000)par exemple.

eyquem
la source
@Lattyware Parce que je suis sûr que l'exécution est plus rapide. D'ailleurs, même lorsque le code ordonne de lire un fichier ligne par ligne, le fichier est lu par morceaux, qui sont mis en cache dans lequel chaque ligne est ensuite lue l'une après l'autre. La meilleure procédure serait de mettre la longueur du morceau de lecture égale à la taille du cache. Mais je ne sais pas comment déterminer la taille de ce cache.
eyquem
C'est l'implémentation dans CPython, mais rien de tout cela n'est garanti. Une telle optimisation est une mauvaise idée, car même si elle peut être efficace sur certains systèmes, elle peut ne pas l'être sur d'autres.
Gareth Latty
1
Oui, bien sûr, la lecture ligne par ligne est mise en mémoire tampon. C'est exactement pourquoi ce n'est pas beaucoup plus lent. (En fait, dans certains cas, cela peut même être légèrement plus rapide, car celui qui a porté Python sur votre plate-forme a choisi une taille de bloc bien meilleure que 10000.) Si les performances de cela comptent vraiment, vous devrez profiler différentes implémentations. Mais 99,99…% du temps, dans les deux cas, c'est plus que suffisamment rapide, ou les E / S disque sont la partie lente et peu importe ce que fait votre code.
abarnert
De plus, si vous avez vraiment besoin d'optimiser manuellement la mise en mémoire tampon, vous voudrez utiliser os.openet os.read, car plain openutilise les wrappers de Python autour du stdio de C, ce qui signifie que 1 ou 2 tampons supplémentaires vous gênent.
abarnert
PS, quant à savoir pourquoi 10000 est mauvais: vos fichiers sont probablement sur un disque, avec des blocs d'une puissance d'octets longs. Disons qu'ils sont 4096 octets. Ainsi, lire 10000 octets signifie lire deux blocs, puis une partie du suivant. Lire un autre 10000 signifie lire le reste du suivant, puis deux blocs, puis une partie du suivant. Comptez le nombre de lectures de bloc partielles ou complètes que vous avez et vous perdez beaucoup de temps. Heureusement, la mise en mémoire tampon et la mise en cache Python, stdio, système de fichiers et noyau vous cacheront la plupart de ces problèmes, mais pourquoi essayer de les créer en premier lieu?
abarnert
0
def concatFiles():
    path = 'input/'
    files = os.listdir(path)
    for idx, infile in enumerate(files):
        print ("File #" + str(idx) + "  " + infile)
    concat = ''.join([open(path + f).read() for f in files])
    with open("output_concatFile.txt", "w") as fo:
        fo.write(path + concat)

if __name__ == "__main__":
    concatFiles()
user2825287
la source
-2
  import os
  files=os.listdir()
  print(files)
  print('#',tuple(files))
  name=input('Enter the inclusive file name: ')
  exten=input('Enter the type(extension): ')
  filename=name+'.'+exten
  output_file=open(filename,'w+')
  for i in files:
    print(i)
    j=files.index(i)
    f_j=open(i,'r')
    print(f_j.read())
    for x in f_j:
      outfile.write(x)
VasanthOPT
la source