Python nối các tệp văn bản


168

Tôi có một danh sách 20 tên tập tin, như ['file1.txt', 'file2.txt', ...]. Tôi muốn viết một tập lệnh Python để ghép các tệp này thành một tệp mới. Tôi có thể mở từng tệp bằng cách f = open(...)đọc từng dòng bằng cách gọi f.readline()và viết từng dòng vào tệp mới đó. Nó không có vẻ rất "thanh lịch" đối với tôi, đặc biệt là phần tôi phải đọc // viết từng dòng một.

Có cách nào "thanh lịch" hơn để làm điều này trong Python không?


7
Nó không phải là python, nhưng trong kịch bản shell bạn có thể làm một cái gì đó như cat file1.txt file2.txt file3.txt ... > output.txt. Trong python, nếu bạn không thích readline(), luôn luôn có readlines()hoặc đơn giản read().
jedwards

1
@jedwards chỉ cần chạy cat file1.txt file2.txt file3.txtlệnh bằng subprocessmô-đun và bạn đã hoàn tất. Nhưng tôi không chắc chắn nếu catlàm việc trong các cửa sổ.
Ashwini Chaudhary

5
Một lưu ý, cách bạn mô tả là một cách khủng khiếp để đọc một tập tin. Sử dụng withcâu lệnh để đảm bảo các tệp của bạn được đóng đúng cách và lặp lại tệp để nhận các dòng, thay vì sử dụng f.readline().
Gareth Latty

@jedwards cat không hoạt động khi tệp văn bản là unicode.
Avi Cohen

Phân tích thực tế waymoot.org/home/python_opes
nu everest

Câu trả lời:


258

Điều này nên làm điều đó

Đối với các tệp lớn:

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)

Đối với các tệp nhỏ:

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

Càng và một điều thú vị khác mà tôi nghĩ đến :

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)

Đáng buồn thay, phương pháp cuối cùng này để lại một vài mô tả tập tin mở, mà GC nên chăm sóc bằng mọi cách. Tôi chỉ nghĩ rằng nó là thú vị


9
Điều này sẽ, đối với các tệp lớn, sẽ rất kém hiệu quả bộ nhớ.
Gareth Latty

1
@ InspectorG4dget: Tôi đã không hỏi bạn, tôi đã hỏi Eyquem, người phàn nàn rằng giải pháp của bạn sẽ không hiệu quả. Tôi sẵn sàng đặt cược rằng nó đủ hiệu quả hơn cho trường hợp sử dụng của OP và cho bất kỳ trường hợp sử dụng nào mà Eyquem có trong tâm trí. Nếu anh ta nghĩ rằng nó không phải, thì trách nhiệm của anh ta là phải chứng minh điều đó trước khi yêu cầu bạn tối ưu hóa nó.
abarnert

2
chúng ta đang xem xét một tập tin lớn là gì?
Dee

4
@dee: một tệp quá lớn đến nỗi nội dung của nó không vừa với bộ nhớ chính
InspectorG4dget

7
Chỉ cần nhắc lại: đây là câu trả lời sai, shutil.copyfileobj là câu trả lời đúng.
Paul Crowley

193

Sử dụng shutil.copyfileobj.

Nó tự động đọc các tệp đầu vào từng đoạn cho bạn, sẽ hiệu quả hơn và đọc các tệp đầu vào và sẽ hoạt động ngay cả khi một số tệp đầu vào quá lớn để vừa với bộ nhớ:

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'):tôi cũng đã thay thế câu lệnh for để bao gồm tất cả các tệp trong thư mục nhưng tôi output_filebắt đầu phát triển rất lớn như trong 100 gb trong thời gian rất nhanh.
R__raki__

10
Lưu ý, đó là hợp nhất các chuỗi cuối cùng của mỗi tệp với các chuỗi đầu tiên của tệp tiếp theo nếu không có các ký tự EOL. Trong trường hợp của tôi, tôi đã nhận được kết quả hoàn toàn bị hỏng sau khi sử dụng mã này. Tôi đã thêm wfd.write (b "\ n") sau khi copyfileobj để có kết quả bình thường
Thelambofgoat

1
@Thelambofgoat Tôi sẽ nói rằng đó không phải là một sự kết hợp thuần túy trong trường hợp đó, nhưng hey, bất cứ điều gì phù hợp với nhu cầu của bạn.
HelloGoodbye

59

Đó chính xác là những gì fileinput dành cho:

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

Đối với trường hợp sử dụng này, nó thực sự không đơn giản hơn nhiều so với việc lặp lại các tệp theo cách thủ công, nhưng trong các trường hợp khác, có một trình vòng lặp duy nhất lặp lại trên tất cả các tệp như thể chúng là một tệp duy nhất rất tiện dụng. (Ngoài ra, việc fileinputđóng từng tệp ngay khi hoàn thành nghĩa là không cần withhoặc mỗi tệp close, nhưng đó chỉ là một khoản tiết kiệm một dòng, không phải là vấn đề lớn.)

Có một số tính năng tiện lợi khác fileinput, như khả năng thực hiện sửa đổi tại chỗ các tệp chỉ bằng cách lọc từng dòng.


Như đã lưu ý trong các bình luận và được thảo luận trong một bài đăng khác , fileinputcho Python 2.7 sẽ không hoạt động như được chỉ ra. Ở đây sửa đổi một chút để làm cho mã Python 2.7 tuân thủ

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

@Lattyware: Tôi nghĩ rằng hầu hết những người tìm hiểu fileinputđều nói rằng đó là cách biến một thứ đơn giản sys.argv(hoặc những gì còn lại như sau optparse/ v.v.) thành một tệp ảo lớn cho các tập lệnh tầm thường, và đừng nghĩ sử dụng nó cho bất cứ điều gì khác (nghĩa là khi danh sách không phải là dòng lệnh args). Hoặc họ học, nhưng sau đó quên đi Tôi vẫn tiếp tục khám phá nó mỗi năm hoặc hai lần
abarnert

1
@abament Tôi nghĩ for line in fileinput.input()không phải là cách tốt nhất để chọn trong trường hợp cụ thể này: OP muốn nối các tệp, không đọc từng dòng một quá trình về mặt lý thuyết để thực hiện
Eyquem

1
@eyquem: Đó không phải là một quá trình dài hơn để thực thi. Như chính bạn đã chỉ ra, các giải pháp dựa trên dòng không đọc một ký tự một lần; họ đọc theo từng đoạn và kéo các dòng ra khỏi bộ đệm. Thời gian I / O sẽ hoàn toàn thay đổi thời gian phân tích dòng, miễn là người triển khai không làm điều gì đó ngu ngốc khủng khiếp trong bộ đệm, nó sẽ nhanh như vậy (và thậm chí còn nhanh hơn cả việc cố gắng đoán tại một bộ đệm tốt kích thước bản thân, nếu bạn nghĩ 10000 là một lựa chọn tốt).
abarnert

1
@abarnert KHÔNG, 10000 không phải là một lựa chọn tốt. Đây thực sự là một lựa chọn rất tồi bởi vì nó không phải là sức mạnh của 2 và nó có kích thước nhỏ một cách lố bịch. Kích thước tốt hơn sẽ là 2097152 (2 21), 16777216 (2 24) hoặc thậm chí 134217728 (2 ** 27), tại sao không?, 128 MB không là gì trong RAM 4 GB.
Eyquem

2
Mã ví dụ không hoàn toàn hợp lệ cho Python 2.7.10 trở lên: stackoverflow.com/questions/30835090/iêu
CnrL

8

Tôi không biết về sự thanh lịch, nhưng điều này hoạt động:

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

8
bạn thậm chí có thể tránh vòng lặp: nhập os; os.system ("tệp mèo * .txt >> OutFile.txt")
lib

6
không đa nền tảng và sẽ phá vỡ tên tệp có khoảng trắng trong đó
cừu bay

3
Điều này là không an toàn; Ngoài ra, catcó thể lấy một danh sách các tập tin, vì vậy không cần phải gọi nó nhiều lần. Bạn có thể dễ dàng làm cho nó an toàn bằng cách gọi subprocess.check_callthay vìos.system
Clément

5

Có gì sai với các lệnh UNIX? (cho rằng bạn không làm việc trên Windows):

ls | xargs cat | tee output.txt thực hiện công việc (bạn có thể gọi nó từ python với quy trình con nếu bạn muốn)


21
bởi vì đây là một câu hỏi về trăn
ObscureRobot

2
Nói chung không có gì sai, nhưng câu trả lời này đã bị hỏng (không chuyển đầu ra của ls sang xargs, chỉ cần chuyển trực tiếp danh sách các tệp cho mèo cat * | tee output.txt:).
Clément

Nếu nó có thể chèn tên tập tin đó sẽ là tuyệt vời.
De Khánh

@De Khánh Để chỉ định tên tệp đầu vào, bạn có thể sử dụngcat file1.txt file2.txt | tee output.txt
GoTrained

1
... và bạn có thể vô hiệu hóa việc gửi đến thiết bị xuất chuẩn (in trong Terminal) bằng cách thêm 1> /dev/nullvào cuối lệnh
GoTrained

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

Một điểm chuẩn đơn giản cho thấy rằng tắt máy hoạt động tốt hơn.


3

Một thay thế cho câu trả lời @ InspectorG4dget (câu trả lời tốt nhất cho đến ngày 29-03-2016). Tôi đã thử nghiệm với 3 tệp 436MB.

@ InspectorG4dget giải pháp: 162 giây

Giải pháp sau: 125 giây

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

Ý tưởng là tạo một tệp bó và thực thi nó, tận dụng "công nghệ tốt cũ". Nó bán trăn nhưng hoạt động nhanh hơn. Hoạt động cho các cửa sổ.


3

Nếu bạn có nhiều tệp trong thư mục thì glob2có thể là một lựa chọn tốt hơn để tạo danh sách tên tệp thay vì viết chúng bằng tay.

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

2

Kiểm tra phương thức .read () của đối tượng Tệp:

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

Bạn có thể làm một cái gì đó như:

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

hoặc một con trăn 'thanh lịch' hơn:

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

trong đó, theo bài viết này: http://www.skymind.com/~ocrow/python_opes/ cũng sẽ là nhanh nhất.


10
Điều này sẽ tạo ra một chuỗi khổng lồ, tùy thuộc vào kích thước của các tệp, có thể lớn hơn bộ nhớ khả dụng. Vì Python cung cấp quyền truy cập lười biếng vào các tệp dễ dàng, đó là một ý tưởng tồi.
Gareth Latty

2

Nếu các tệp không phải là khổng lồ:

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

Nếu các tệp quá lớn để có thể đọc và giữ hoàn toàn trong RAM, thuật toán phải khác một chút để đọc từng tệp được sao chép trong một vòng lặp bởi các đoạn có độ dài cố định, read(10000)ví dụ sử dụng .


@Lattyware Vì tôi khá chắc rằng việc thực thi nhanh hơn. Trên thực tế, ngay cả khi mã yêu cầu đọc một dòng tệp theo từng dòng, tệp được đọc theo từng đoạn, được đặt trong bộ đệm trong đó mỗi dòng được đọc lần lượt từng dòng. Quy trình tốt hơn sẽ là đặt độ dài của đoạn đọc bằng với kích thước của bộ đệm. Nhưng tôi không biết làm thế nào để xác định kích thước của bộ đệm này.
Eyquem

Đó là việc thực hiện trong CPython, nhưng không có gì được đảm bảo. Tối ưu hóa như thế là một ý tưởng tồi vì mặc dù nó có thể hiệu quả trên một số hệ thống, nhưng nó có thể không hiệu quả trên các hệ thống khác.
Gareth Latty

1
Vâng, tất nhiên việc đọc từng dòng được đệm. Đó chính xác là lý do tại sao nó không chậm hơn nhiều. (Trên thực tế, trong một số trường hợp, nó thậm chí có thể nhanh hơn một chút, bởi vì bất kỳ ai đã chuyển Python sang nền tảng của bạn đều chọn kích thước khối tốt hơn nhiều so với 10000.) Nếu hiệu suất của việc này thực sự quan trọng, bạn sẽ phải lập hồ sơ triển khai khác nhau. Nhưng 99,99%% thời gian, dù bằng cách nào cũng đủ nhanh, hoặc I / O trên đĩa thực tế là phần chậm và không quan trọng mã của bạn làm gì.
abarnert

Ngoài ra, nếu bạn thực sự cần tối ưu hóa bộ đệm theo cách thủ công, bạn sẽ muốn sử dụng os.openos.read, bởi vì opensử dụng các trình bao bọc của Python xung quanh stdio của C, có nghĩa là 1 hoặc 2 bộ đệm bổ sung cản trở bạn.
abarnert

PS, như lý do tại sao 10000 là xấu: Các tệp của bạn có thể nằm trên một đĩa, với các khối có sức mạnh của byte dài. Giả sử chúng là 4096 byte. Vì vậy, đọc 10000 byte có nghĩa là đọc hai khối, sau đó là một phần của khối tiếp theo. Đọc 10000 khác có nghĩa là đọc phần còn lại của tiếp theo, sau đó là hai khối, sau đó là một phần tiếp theo. Đếm số lượng khối đọc một phần hoặc toàn bộ mà bạn có và bạn đang lãng phí rất nhiều thời gian. May mắn thay, Python, stdio, hệ thống tập tin và bộ đệm và bộ đệm kernel sẽ ẩn hầu hết các vấn đề này với bạn, nhưng tại sao lại cố gắng tạo chúng ở nơi đầu tiên?
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()

-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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.