Python menyatukan file teks

168

Saya memiliki daftar 20 nama file, seperti ['file1.txt', 'file2.txt', ...]. Saya ingin menulis skrip Python untuk menggabungkan file-file ini menjadi file baru. Saya bisa membuka setiap file dengan f = open(...), membaca baris demi baris dengan menelepon f.readline(), dan menulis setiap baris ke file baru itu. Rasanya tidak "elegan" bagi saya, terutama bagian di mana saya harus membaca // menulis baris demi baris.

Apakah ada cara yang lebih "elegan" untuk melakukan ini dengan Python?

JJ Beck
sumber
7
Ini bukan python, tetapi dalam skrip shell Anda bisa melakukan sesuatu seperti cat file1.txt file2.txt file3.txt ... > output.txt. Dengan python, jika Anda tidak suka readline(), selalu ada readlines()atau sederhana read().
Juni
1
@jedwards cukup jalankan cat file1.txt file2.txt file3.txtperintah menggunakan subprocessmodul dan Anda selesai. Tapi saya tidak yakin apakah catberfungsi di windows.
Ashwini Chaudhary
5
Sebagai catatan, cara Anda menggambarkan adalah cara yang mengerikan untuk membaca file. Gunakan withpernyataan untuk memastikan file Anda ditutup dengan benar, dan beralihlah ke file untuk mendapatkan baris, daripada menggunakan f.readline().
Gareth Latty
@jedwards cat tidak berfungsi saat file teks unicode.
Avi Cohen
Analisis aktual waymoot.org/home/python_string
nu everest

Jawaban:

258

Ini harus dilakukan

Untuk file besar:

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)

Untuk file kecil:

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

… Dan satu lagi yang menarik yang saya pikirkan :

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)

Sayangnya, metode terakhir ini menyisakan beberapa deskriptor file terbuka, yang harus dijaga oleh GC. Saya hanya berpikir itu menarik

inspectorG4dget
sumber
9
Ini akan, untuk file besar, menjadi sangat tidak efisien memori.
Gareth Latty
1
@ inspectorG4dget: Saya tidak meminta Anda, saya bertanya kepada eyquem, yang mengeluh bahwa solusi Anda tidak akan efisien. Saya berani bertaruh itu lebih dari cukup efisien untuk kasus penggunaan OP, dan untuk apa pun kasus penggunaan yang ada di benak. Jika menurutnya itu bukan tanggung jawabnya, buktikan sebelum meminta Anda mengoptimalkannya.
abarnert
2
apa yang kita pertimbangkan untuk file besar ?
Dee
4
@dee: file yang sangat besar sehingga isinya tidak masuk ke memori utama
inspectorG4dget
7
Hanya untuk mengulangi: ini adalah jawaban yang salah, shutil.copyfileobj adalah jawaban yang tepat.
Paul Crowley
193

Gunakan shutil.copyfileobj.

Secara otomatis membaca file input potongan demi sepotong untuk Anda, yang lebih efisien dan membaca file input dan akan bekerja bahkan jika beberapa file input terlalu besar untuk masuk ke dalam memori:

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)
meong
sumber
2
for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'):baik saya mengganti pernyataan untuk memasukkan semua file dalam direktori tetapi saya output_filemulai tumbuh sangat besar seperti di 100's of gb dalam waktu yang sangat cepat.
R__raki__
10
Catatan, yaitu akan menggabungkan string terakhir dari setiap file dengan string pertama dari file berikutnya jika tidak ada karakter EOL. Dalam kasus saya, saya mendapatkan hasil yang benar-benar rusak setelah menggunakan kode ini. Saya menambahkan wfd.write (b "\ n") setelah copyfileobj untuk mendapatkan hasil yang normal
Thelambofgoat
1
@ Thelambofgoat Saya akan mengatakan bahwa itu bukan rangkaian murni dalam kasus itu, tapi hei, apa pun yang sesuai dengan kebutuhan Anda.
HelloGoodbye
59

Itulah yang fileinput adalah untuk:

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

Untuk kasus penggunaan ini, itu sebenarnya tidak jauh lebih sederhana daripada hanya iterasi file secara manual, tetapi dalam kasus lain, memiliki iterator tunggal yang melakukan iterasi pada semua file seolah-olah mereka adalah file tunggal sangat berguna. (Juga, fakta bahwa fileinputmenutup setiap file segera setelah selesai berarti tidak perlu withatau closemasing - masing, tapi itu hanya penghematan satu baris, bukan masalah besar.)

Ada beberapa fitur bagus lainnya fileinput, seperti kemampuan untuk melakukan modifikasi di tempat file hanya dengan memfilter setiap baris.


Seperti disebutkan dalam komentar, dan dibahas dalam posting lain , fileinputuntuk Python 2.7 tidak akan berfungsi seperti yang ditunjukkan. Berikut sedikit modifikasi untuk membuat kode Python 2.7 sesuai

with open('outfilename', 'w') as fout:
    fin = fileinput.input(filenames)
    for line in fin:
        fout.write(line)
    fin.close()
abarnert
sumber
@ Lattyware: Saya pikir sebagian besar orang yang belajar fileinputdiberitahu bahwa ini adalah cara untuk mengubah yang sederhana sys.argv(atau apa yang tersisa sebagai argumen setelah optparse/ dll.) Menjadi file virtual besar untuk skrip sepele, dan jangan berpikir untuk menggunakannya untuk apa pun lain (yaitu, ketika daftar bukan argumen baris perintah). Atau mereka memang belajar, tetapi kemudian lupa — saya terus menemukannya kembali setiap atau dua tahun sekali ...
abarnert
1
@abament Saya pikir for line in fileinput.input()bukan cara terbaik untuk memilih dalam kasus khusus ini: OP ingin menyatukan file, tidak membacanya baris demi baris yang secara teoritis merupakan proses yang lebih lama untuk dieksekusi
eyquem
1
@eyquem: Ini bukan proses yang lebih lama untuk dieksekusi. Seperti yang Anda tunjukkan sendiri, solusi berbasis garis tidak membaca satu karakter pada satu waktu; mereka membaca dalam potongan-potongan dan menarik garis keluar dari buffer. Waktu I / O benar-benar akan membanjiri waktu baris-parsing, jadi selama implementor tidak melakukan sesuatu yang sangat bodoh dalam buffering, itu akan sama cepat (dan mungkin bahkan lebih cepat daripada mencoba menebak buffer yang baik) ukur sendiri, jika Anda berpikir 10.000 adalah pilihan yang baik).
abarnert
1
@abarnert NO, 10000 bukan pilihan yang baik. Itu memang pilihan yang sangat buruk karena itu bukan kekuatan 2 dan itu adalah ukuran yang sangat kecil. Ukuran yang lebih baik adalah 2097152 (2 21), 16777216 (2 24) atau bahkan 134217728 (2 ** 27), mengapa tidak?, 128 MB bukan apa-apa dalam RAM 4 GB.
eyquem
2
Kode contoh tidak cukup valid untuk Python 2.7.10 dan yang lebih baru: stackoverflow.com/questions/30835090/…
CnrL
8

Saya tidak tahu tentang keanggunan, tetapi ini bekerja:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")
Daniel
sumber
8
Anda bahkan dapat menghindari loop: import os; os.system ("file cat * .txt >> OutFile.txt")
lib
6
tidak lintas platform dan akan merusak nama file dengan spasi di dalamnya
menerbangkan domba
3
Ini tidak aman; juga, catbisa mengambil daftar file, jadi tidak perlu berulang kali menyebutnya. Anda dapat dengan mudah membuatnya aman dengan menelepon subprocess.check_callalih-alihos.system
Clément
5

Apa yang salah dengan perintah UNIX? (mengingat Anda tidak bekerja di Windows):

ls | xargs cat | tee output.txt melakukan pekerjaan (Anda dapat memanggilnya dari python dengan subprocess jika Anda mau)

lucasg
sumber
21
karena ini adalah pertanyaan tentang python.
ObscureRobot
2
Tidak ada yang salah secara umum, tetapi jawaban ini rusak (jangan meneruskan output dari ls ke xargs, hanya meneruskan daftar file ke cat secara langsung:) cat * | tee output.txt.
Clément
Jika bisa memasukkan nama file juga itu akan bagus.
Deqing
@Deqing Untuk menentukan nama file input, Anda dapat menggunakancat file1.txt file2.txt | tee output.txt
GoTrained
1
... dan Anda dapat menonaktifkan pengiriman ke stdout (mencetak di Terminal) dengan menambahkan 1> /dev/nulldi akhir perintah
GoTrained
4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Tolok ukur sederhana menunjukkan bahwa shutil berkinerja lebih baik.

haoming
sumber
3

Alternatif jawaban @ inspectorG4dget (jawaban terbaik hingga tanggal 29-03-2016). Saya diuji dengan 3 file 436MB.

solusi @ inspectorG4dget: 162 detik

Solusi berikut: 125 detik

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

Idenya adalah untuk membuat file batch dan menjalankannya, mengambil keuntungan dari "teknologi lama yang baik". Semi-python tetapi bekerja lebih cepat. Bekerja untuk windows.

João Palma
sumber
3

Jika Anda memiliki banyak file di direktori maka glob2mungkin merupakan opsi yang lebih baik untuk menghasilkan daftar nama file daripada menulisnya dengan tangan.

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
sumber
2

Lihat metode .read () dari objek File:

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

Anda dapat melakukan sesuatu seperti:

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

atau cara python yang lebih 'elegan':

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

yang, menurut artikel ini: http://www.skymind.com/~ocrow/python_string/ juga akan menjadi yang tercepat.

Alex Kawrykow
sumber
10
Ini akan menghasilkan string raksasa, yang, tergantung pada ukuran file, bisa lebih besar dari memori yang tersedia. Karena Python menyediakan akses malas ke file, itu ide yang buruk.
Gareth Latty
2

Jika file tidak raksasa:

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

Jika file terlalu besar untuk sepenuhnya dibaca dan disimpan dalam RAM, algoritme harus sedikit berbeda untuk membaca setiap file yang akan disalin dalam satu lingkaran dengan potongan dengan panjang tetap, menggunakan read(10000)misalnya.

eyquem
sumber
@ Bug Karena saya yakin pelaksanaannya lebih cepat. By the way, pada kenyataannya, bahkan ketika kode memerintahkan untuk membaca file baris demi baris, file tersebut dibaca oleh potongan, yang dimasukkan ke dalam cache di mana setiap baris kemudian dibaca satu demi satu. Prosedur yang lebih baik adalah dengan meletakkan panjang read chunk sama dengan ukuran cache. Tapi saya tidak tahu cara menentukan ukuran cache ini.
eyquem
Itu implementasi di CPython, tapi tidak ada yang dijamin. Mengoptimalkan seperti itu adalah ide yang buruk karena sementara itu mungkin efektif pada beberapa sistem, itu mungkin tidak pada yang lain.
Gareth Latty
1
Ya, tentu saja pembacaan baris demi baris buffered. Itulah mengapa tidak terlalu lambat. (Bahkan, dalam beberapa kasus, bahkan mungkin sedikit lebih cepat, karena siapa pun yang mem-porting Python ke platform Anda memilih ukuran chunk yang jauh lebih baik daripada 10000). Jika kinerja ini benar-benar penting, Anda harus membuat profil implementasi yang berbeda. Tapi 99,99 ...% dari waktu, baik cara lebih dari cukup cepat, atau I / O disk yang sebenarnya adalah bagian yang lambat dan tidak masalah apa kode Anda.
abarnert
Juga, jika Anda benar-benar perlu mengoptimalkan buffering secara manual, Anda akan ingin menggunakan os.opendan os.read, karena plain openmenggunakan pembungkus Python di sekitar stdio C, yang berarti 1 atau 2 buffer tambahan menghalangi Anda.
abarnert
PS, seperti mengapa 10000 buruk: File Anda mungkin ada di disk, dengan blok yang panjangnya beberapa byte. Katakanlah mereka adalah 4.096 byte. Jadi, membaca 10.000 byte berarti membaca dua blok, lalu bagian selanjutnya. Membaca 10.000 lainnya berarti membaca sisanya, kemudian dua blok, lalu bagian selanjutnya. Hitung berapa banyak blok atau sebagian pembacaan yang Anda miliki, dan Anda menghabiskan banyak waktu. Untungnya, Python, stdio, filesystem, dan buffering dan caching kernel akan menyembunyikan sebagian besar masalah ini dari Anda, tetapi mengapa mencoba membuatnya terlebih dahulu?
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()
pengguna2825287
sumber
-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
sumber