J'ai une liste de 20 noms de fichiers, comme ['file1.txt', 'file2.txt', ...]
. Je veux écrire un script Python pour concaténer ces fichiers dans un nouveau fichier. Je pourrais ouvrir chaque fichier avec f = open(...)
, lire ligne par ligne en appelant f.readline()
et écrire chaque ligne dans ce nouveau fichier. Cela ne me semble pas très "élégant", en particulier la partie où je dois lire // écrire ligne par ligne.
Existe-t-il une manière plus "élégante" de faire cela en Python?
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, que le GC devrait s’occuper de toute façon. Je pensais juste que c'était intéressant
Utilisez shutil.copyfileobj
.
Il lit automatiquement les morceaux d'entrée par morceau pour vous, ce qui est plus efficace et permet de lire les fichiers d'entrée et fonctionne même si certains des fichiers d'entrée sont trop volumineux pour tenir dans la mémoire:
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)
C'est exactement ce que fileinput est pour:
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 plus simple que de simplement parcourir les fichiers manuellement, 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 que fileinput
ferme chaque fichier dès qu'il est terminé signifie qu'il n'est pas nécessaire de with
ou close
chacun, mais ce n'est qu'une économie d'une ligne, pas si grande d'un accord.)
Il existe d'autres fonctionnalités intéressantes dans fileinput
, comme la possibilité d'effectuer des modifications sur place de fichiers en filtrant chaque ligne.
Comme indiqué dans les commentaires et discuté dans un autre post , fileinput
pour Python 2.7 ne fonctionnera pas comme indiqué. Voici une légère modification pour rendre le code Python 2.7 conforme
with open('outfilename', 'w') as fout:
fin = fileinput.input(filenames)
for line in fin:
fout.write(line)
fin.close()
Je ne sais pas à propos de l'élégance, mais cela fonctionne:
import glob
import os
for f in glob.glob("file*.txt"):
os.system("cat "+f+" >> OutFile.txt")
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 de python avec un sous-processus si vous le souhaitez)
Une alternative à la réponse @ inspectorG4dget (meilleure réponse à ce jour du 29-03-2016). J'ai testé avec 3 fichiers de 436MB.
@ inspectorG4dget solution: 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 de commandes et de l'exécuter, en tirant parti de la "vieille technologie". Son semi-python mais travaille plus vite. Fonctionne pour les fenêtres.
Découvrez la méthode .read () de l'objet File:
http://docs.python.org/2/tutorial/inputoutput.html#methods-of-file-objects
Vous pourriez faire quelque chose comme:
concat = ""
for file in files:
concat += open(file).read()
ou une façon plus "élégante" de python:
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.
outfile.write(infile.read()) 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) 0.60599684715271s
Un simple point de repère montre que le shutil fonctionne mieux.
Si vous avez beaucoup de fichiers dans le répertoire, alors glob2
pourrait être une meilleure option pour 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')
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 volumineux 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 fragments de longueur fixe, en utilisant read(10000)
par exemple.
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 __== "__main__":
concatFiles()
Ceci est une représentation Python 3.
from pathlib import Path
filenames = ['file1.txt', 'file2.txt', ...]
output_file = Path('output_file.txt')
for file in file_list:
output_file.write_text(Path(file).read_text())