Python metin dosyalarını birleştirir


168

20 dosya adından oluşan bir listem var ['file1.txt', 'file2.txt', ...]. Bu dosyaları yeni bir dosyaya birleştirmek için bir Python betiği yazmak istiyorum. Her dosyayı açarak, f = open(...)satır satır okuyarak okuyabilir f.readline()ve her satırı bu yeni dosyaya yazabilirim. Bana göre çok "zarif" görünmüyor, özellikle okuduğum / satır satır yazdığım kısım.

Python'da bunu yapmanın daha "zarif" bir yolu var mı?


7
Python değil, ama kabuk komut dosyasında böyle bir şey yapabilirdiniz cat file1.txt file2.txt file3.txt ... > output.txt. Python'da, beğenmezseniz readline(), her zaman readlines()veya basitçe vardır read().
jedwards

1
@jedwards, modülü cat file1.txt file2.txt file3.txtkullanarak komutu çalıştırmanız yeterlidir subprocess. Ama catpencerelerde çalışıp çalışmadığından emin değilim .
Ashwini Chaudhary

5
Not olarak, tarif ettiğiniz yol bir dosyayı okumak için korkunç bir yoldur. withDosyalarınızın düzgün bir şekilde kapatıldığından emin olmak için ifadeyi kullanın ve kullanmak yerine satır almak için dosya üzerinde tekrarlayın f.readline().
Gareth Latty

@jedwards cat metin dosyası unicode olduğunda çalışmaz.
Avi Cohen

Yanıtlar:


260

Bunu yapmalı

Büyük dosyalar için:

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)

Küçük dosyalar için:

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

… Ve düşündüğüm bir başka ilginç konu :

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)

Ne yazık ki, bu son yöntem GC'nin yine de ilgilenmesi gereken birkaç açık dosya tanımlayıcısı bırakır. Sadece ilginç olduğunu düşündüm


9
Bu, büyük dosyalar için bellekte yetersizlik sağlar.
Gareth Latty

1
@ inspectorG4dget: Size sormuyordum, çözümünüzün verimli olmayacağından şikayet eden kaş döküyordum. Bahse girmeye hazırım, OP'nin kullanım durumu ve kullanım şekli eyquem'in aklındaki her şey için yeterince verimli. Eğer olmadığını düşünüyorsa, optimize etmenizi talep etmeden önce bunu kanıtlamak onun sorumluluğundadır.
abarnert

2
ne bir değerlendiriyorlar büyük dosya olunur?
Dee

4
@dee: içeriği ana belleğe sığmayacak kadar büyük bir dosya
inspectorG4dget

8
Tekrarlamak gerekirse: bu yanlış cevaptır, shutil.copyfileobj doğru cevaptır.
Paul Crowley

193

Kullanın shutil.copyfileobj.

Giriş dosyalarını sizin için yığın olarak otomatik olarak okur, bu daha verimli ve giriş dosyalarını okur ve bazı giriş dosyaları belleğe sığmayacak kadar büyük olsa bile çalışır:

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)

2
for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'):iyi i dizinindeki tüm dosyaları dahil etmek için for deyimi değiştirdi ama benim output_fileçok hızlı bir şekilde 100 's gb gibi gerçekten büyük büyümeye başladı.
R__raki__

10
EOL karakteri yoksa, her dosyanın son dizelerini bir sonraki dosyanın ilk dizeleriyle birleştireceğini unutmayın. Benim durumumda bu kodu kullandıktan sonra tamamen bozuk sonuç aldım. Normal sonuç almak için copyfileobj'den sonra wfd.write (b "\ n") ekledim
Thelambofgoat

2
@Thelambofgoat Bu durumda saf bir birleşim değil diyebilirim, ama hey, ihtiyaçlarınız ne olursa olsun.
HelloGoodbye

59

Dosya girişi tam olarak bunun içindir:

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

Bu kullanım durumunda, dosyaları manuel olarak yinelemekten çok daha basit değildir, ancak diğer durumlarda, tek bir dosya gibi tüm dosyaları yineleyen tek bir yineleyiciye sahip olmak çok kullanışlıdır. (Ayrıca, fileinputher dosyayı biter bitmez kapatması gerçeği withveya closeher birine gerek olmadığı anlamına gelir , ancak bu sadece bir satırlık bir tasarruf, o kadar da büyük bir anlaşma değil.)

fileinputSadece her satırı filtreleyerek dosyaların yerinde değişiklik yapabilmesi gibi bazı başka şık özellikler de vardır .


Yorumlarda belirtildiği ve başka bir gönderide tartışıldığı gibi , fileinputPython 2.7 için belirtildiği gibi çalışmaz. Burada Python 2.7 kodunu uyumlu hale getirmek için küçük değişiklikler

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

@Lattyware: Sanırım öğrenen çoğu kişiye fileinputbasit sys.argv(ya da optparse/ etc'den sonra argümanlar olarak kalanlar ) önemsiz senaryolar için büyük bir sanal dosyaya dönüştürmenin bir yolu olduğu söyleniyor ve bunu herhangi bir şey için kullanmayı düşünmüyorum else (yani, liste komut satırı argümanları değilse). Ya da öğreniyorlar ama unutuyorlar - her iki yılda bir yeniden keşfetmeye devam ediyorum…
abarnert

1
@abament for line in fileinput.input()Bu özel durumda seçmenin en iyi yolu olmadığını düşünüyorum : OP dosyaları birleştirmek istiyor, bunları satır satır okumak istemiyor, bu da teorik olarak daha uzun bir süreç
yürütüyor

1
@eyquem: Yürütülmesi daha uzun bir süreç değil. Sizin de belirttiğiniz gibi, satır tabanlı çözümler bir seferde bir karakter okumaz; parçalar halinde okur ve satırları arabellekten çıkarırlar. G / Ç süresi satır ayrıştırma süresini tamamen değiştirecektir, böylece uygulayıcı tamponlamada korkunç bir aptalca bir şey yapmadığı sürece, iyi bir tamponda tahmin etmeye çalışmaktan daha hızlı (ve muhtemelen daha hızlı olacaktır) 10000 iyi bir seçim olduğunu düşünüyorsanız, kendinizi boyutlandırın).
abarnert

1
@abarnert NO, 10000 iyi bir seçim değil. Gerçekten çok kötü bir seçim çünkü 2'nin gücü değil ve gülünç küçük bir boyut. Daha iyi boyutlar 2097152 (2 21), 16777216 (2 24) veya hatta 134217728 (2 ** 27) olabilir, neden olmasın?, 128 MB 4 GB RAM'de hiçbir şey değildir.
eyquem

2
Örnek kod Python 2.7.10 ve üstü için tam olarak geçerli değil: stackoverflow.com/questions/30835090/…
CnrL

7

Zarafeti bilmiyorum, ama bu işe yarıyor:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")

8
döngüden bile kaçınabilirsiniz: import os; os.system ("kedi dosyası * .txt >> OutFile.txt")
lib

7
çapraz platform değil ve içinde boşluk olan dosya adları için kırılacak
uçan koyun

4
Bu güvensizdir; Ayrıca, catbir dosya listesi alabilir, bu yüzden tekrar tekrar aramaya gerek yok. Kolayca arayarak güvenli yapabilirsiniz subprocess.check_callyerineos.system
Clément

5

UNIX komutlarında sorun nedir? (Windows üzerinde çalışmadığınız göz önüne alındığında):

ls | xargs cat | tee output.txt işi yaparsa (isterseniz alt işlem ile python'dan arayabilirsiniz)


21
çünkü bu python ile ilgili bir soru.
ObscureRobot

2
Genel olarak yanlış bir şey yok, ama bu cevap bozuldu (ls çıkışını xargs'a iletmeyin, sadece dosya listesini doğrudan kediye geçirin :) cat * | tee output.txt).
Clément

Dosya adı da ekleyebilirse bu harika olur.
Deqing

@Deqing Girdi dosya adlarını belirtmek için aşağıdakileri kullanabilirsinizcat file1.txt file2.txt | tee output.txt
GoTrained

1
... ve 1> /dev/nullkomutun sonuna ekleyerek stdout'a (Terminalde yazdırma) göndermeyi devre dışı bırakabilirsiniz
GoTrained

4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Basit bir kıyaslama, kapağın daha iyi performans gösterdiğini gösterir.


3

@ İnspectorG4dget yanıtına bir alternatif (29-03-2016 tarihine kadar en iyi yanıt). 3 adet 436 MB dosya ile test ettim.

@ inspectorG4dget çözümü: 162 saniye

Aşağıdaki çözüm: 125 saniye

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

Fikir bir toplu iş dosyası oluşturmak ve yürütmek, "eski iyi teknoloji" yararlanmak. Yarı python ama daha hızlı çalışır. Windows için çalışır.


2

Dosyalar devasa değilse:

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

Dosyalar tamamen okunamayacak ve RAM'de tutulamayacak kadar büyükse, algoritma, örneğin bir döngüde kopyalanacak her dosyayı, sabit uzunluktaki parçalar tarafından okumak için biraz farklı olmalıdır read(10000).


@Lattyware Çünkü yürütmenin daha hızlı olduğundan eminim. Bu arada, aslında, kod bir dosyayı satır satır okuma emri verdiğinde bile, dosya her satırın birbiri ardına okunduğu önbelleğe konulan parçalar tarafından okunur. Daha iyi prosedür, okuma yığınının uzunluğunu önbellek boyutuna eşitlemek olacaktır. Ama bu önbelleğin boyutunu nasıl belirleyeceğimi bilmiyorum.
eyquem

CPython'daki uygulama budur, ancak bunların hiçbiri garanti edilmez. Böyle optimize etmek kötü bir fikirdir, çünkü bazı sistemlerde etkili olabilirken diğerlerinde etkili olmayabilir.
Gareth Latty

1
Evet, elbette satır satır okuma arabelleğe alındı. Bu yüzden o kadar yavaş değil. (Aslında, bazı durumlarda, biraz daha hızlı olabilir, çünkü Python'u platformunuza taşıyan kişi 10000'den daha iyi bir yığın boyutu seçti.) Bunun performansı gerçekten önemliyse, farklı uygulamaları profillemeniz gerekir. Ancak% 99,99… zamanın her iki yolu da yeterince hızlıdır veya gerçek disk G / Ç yavaş kısmıdır ve kodunuzun ne yaptığı önemli değildir.
abarnert

Ayrıca, tamponlamayı gerçekten manuel olarak optimize etmeniz gerekiyorsa, kullanmak isteyeceksiniz os.openve os.readdüz open, Python'un sarmalayıcılarını C'nin stdio'sunda kullandığından, 1 veya 2 ekstra tamponun yolunuza çıkması anlamına gelir.
abarnert

PS, neden 10000'in kötü olduğuna gelince: Dosyalarınız büyük olasılıkla bir bayt uzunluğunda olan bloklarla diskte. Diyelim ki bunlar 4096 bayt. Yani, 10000 bayt okumak iki bloğu, ardından bir sonraki parçayı okumak demektir. Başka bir 10000 okumak, bir sonrakinin geri kalanını, sonra iki bloğu, ardından bir sonrakinin bir kısmını okumak anlamına gelir. Kaç tane kısmi veya tam blok okuduğunuzu sayın ve çok fazla zaman harcıyorsunuz. Neyse ki, Python, stdio, dosya sistemi ve çekirdek arabelleğe alma ve önbellekleme bu sorunların çoğunu sizden gizleyecektir, ancak neden bunları ilk etapta oluşturmaya çalışalım?
abarnert

2

Dizinde dosyaların bir şey var, o zaman glob2onları eliyle yazmak yerine dosya adları listesini oluşturmak için daha iyi bir seçenek olabilir.

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

Bunun soru ile ne ilgisi var? Neden kullanmak glob2yerine globmodül veya içinde globbing işlevselliği pathlib?
AMC

1

File nesnesinin .read () yöntemine bakın:

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

Şöyle bir şey yapabilirsiniz:

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

veya daha 'zarif' bir python yolu:

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

Bu makaleye göre: http://www.skymind.com/~ocrow/python_string/ en hızlısıdır.


10
Bu, dosyaların boyutuna bağlı olarak kullanılabilir bellekten daha büyük olabilen dev bir dize üretecektir. Python dosyalara kolay tembel erişim sağladığı için kötü bir fikirdir.
Gareth Latty

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

-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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.