Python ต่อไฟล์ข้อความ


168

ฉันมีรายชื่อ 20 ['file1.txt', 'file2.txt', ...]ชื่อไฟล์เช่น ฉันต้องการเขียนสคริปต์ Python เพื่อเชื่อมไฟล์เหล่านี้เป็นไฟล์ใหม่ ฉันสามารถเปิดแต่ละไฟล์โดยf = open(...)อ่านทีละบรรทัดโดยการโทรf.readline()และเขียนแต่ละบรรทัดลงในไฟล์ใหม่นั้น ดูเหมือนว่า "สง่างาม" สำหรับฉันโดยเฉพาะอย่างยิ่งส่วนที่ฉันต้องอ่าน // เขียนทีละบรรทัด

มีวิธี "หรูหรา" มากขึ้นในการทำเช่นนี้ใน Python หรือไม่?


7
มันไม่ใช่งูหลาม แต่ในเชลล์สคริปต์คุณสามารถทำอะไรcat file1.txt file2.txt file3.txt ... > output.txtได้บ้าง ในหลามถ้าคุณไม่ชอบreadline()ที่มีอยู่เสมอหรือเพียงแค่readlines() read()
jedwards

1
@ jedwards เพียงแค่เรียกใช้cat file1.txt file2.txt file3.txtคำสั่งโดยใช้subprocessโมดูลและคุณทำเสร็จแล้ว แต่ฉันไม่แน่ใจว่าcatทำงานได้ใน windows หรือไม่
Ashwini Chaudhary

5
วิธีที่คุณอธิบายเป็นวิธีที่แย่มากในการอ่านไฟล์ ใช้คำสั่งเพื่อให้แน่ใจว่าไฟล์ของคุณจะถูกปิดอย่างถูกต้องและย้ำกว่าไฟล์ที่จะได้รับสายมากกว่าการใช้with f.readline()
Gareth Latty

@jedwards cat ไม่ทำงานเมื่อไฟล์ข้อความเป็น Unicode
Avi Cohen

การวิเคราะห์ที่เกิดขึ้นจริงwaymoot.org/home/python_string
nu everest

คำตอบ:


260

สิ่งนี้ควรทำ

สำหรับไฟล์ขนาดใหญ่:

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)

สำหรับไฟล์ขนาดเล็ก:

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

... และอีกหนึ่งที่น่าสนใจที่ฉันคิดว่า :

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)

น่าเศร้าที่วิธีสุดท้ายนี้เหลือตัวอธิบายไฟล์ที่เปิดอยู่สองสามตัวซึ่ง GC ควรจะดูแลอยู่ดี ฉันแค่คิดว่ามันน่าสนใจ


9
สำหรับไฟล์ขนาดใหญ่จะไม่มีประสิทธิภาพของหน่วยความจำมาก
Gareth Latty

1
@ inspectorG4dget: ฉันไม่ได้ถามคุณฉันถาม eyquem ที่บ่นว่าวิธีการแก้ปัญหาของคุณจะไม่มีประสิทธิภาพ ฉันยินดีที่จะเดิมพันว่ามันมีประสิทธิภาพเพียงพอสำหรับกรณีการใช้งานของ OP และสำหรับกรณีการใช้งาน eyquem ที่มีอยู่ในใจ หากเขาคิดว่ามันไม่เป็นความรับผิดชอบของเขาที่จะพิสูจน์ว่าก่อนที่จะเรียกร้องให้คุณเพิ่มประสิทธิภาพ
abarnert

2
สิ่งที่เรากำลังพิจารณาไฟล์ขนาดใหญ่ที่จะ?
Dee

4
@dee: ไฟล์ที่มีขนาดใหญ่จนเนื้อหาไม่พอดีกับหน่วยความจำหลัก
inspectorG4dget

8
เพียงเพื่อย้ำ: นี่คือคำตอบที่ผิด shutil.copyfileobj เป็นคำตอบที่ถูกต้อง
Paul Crowley

193

shutil.copyfileobjใช้

มันจะอ่านไฟล์อินพุตโดยอัตโนมัติโดยไฟล์ย่อยสำหรับคุณซึ่งมีประสิทธิภาพมากขึ้นและอ่านไฟล์อินพุตได้และจะทำงานแม้ว่าไฟล์อินพุตบางไฟล์จะใหญ่เกินไปที่จะใส่ในหน่วยความจำ:

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'):ดีฉันแทนที่คำสั่งสำหรับการรวมไฟล์ทั้งหมดในไดเรกทอรี แต่ฉันoutput_fileเริ่มเติบโตอย่างมากเช่นใน 100 ของ GB ในเวลาที่รวดเร็วมาก
R__raki__

10
โปรดทราบว่าจะรวมสตริงสุดท้ายของแต่ละไฟล์ด้วยสตริงแรกของไฟล์ถัดไปหากไม่มีอักขระ EOL ในกรณีของฉันฉันได้รับความเสียหายทั้งหมดหลังจากใช้รหัสนี้ ฉันเพิ่ม wfd.write (b "\ n") หลังจาก copyfileobj เพื่อให้ได้ผลลัพธ์ตามปกติ
Thelambofgoat

2
@Thelambofgoat ฉันจะบอกว่าไม่ใช่การต่อข้อมูลที่บริสุทธิ์ในกรณีนั้น แต่เฮ้อะไรก็ตามที่คุณต้องการ
HelloGoodbye

59

นั่นคือสิ่งที่ไฟล์อินพุตสำหรับ:

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

สำหรับกรณีการใช้งานนี้มันไม่ง่ายกว่าการวนซ้ำไฟล์ด้วยตนเอง แต่ในกรณีอื่น ๆ การมีตัววนซ้ำเดี่ยวที่วนซ้ำไฟล์ทั้งหมดราวกับว่ามันเป็นไฟล์เดียวมีประโยชน์มาก (นอกจากนี้ความจริงที่ว่าfileinputปิดไฟล์แต่ละไฟล์ทันทีที่เสร็จสิ้นหมายความว่าไม่จำเป็นต้องใช้withหรือcloseแต่ละไฟล์ แต่นั่นเป็นเพียงการประหยัดแบบบรรทัดเดียวไม่ใช่เรื่องใหญ่เลย)

มีคุณสมบัติที่ดีอื่น ๆfileinputเช่นความสามารถในการปรับเปลี่ยนไฟล์แบบแทนที่การกรองแต่ละบรรทัด


ตามที่ระบุไว้ในความคิดเห็นและพูดคุยกันในอีกโพสต์ , fileinputหลาม 2.7 จะไม่ทำงานตามที่ระบุ นี่คือการดัดแปลงเล็กน้อยเพื่อให้เป็นไปตามรหัส Python 2.7

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

@ Lattyware: ฉันคิดว่าคนส่วนใหญ่ที่เรียนรู้เกี่ยวกับการfileinputบอกว่ามันเป็นวิธีที่จะทำให้ง่ายsys.argv(หรือสิ่งที่เหลือจาก args หลังจากoptparse/ etc.) เป็นไฟล์เสมือนขนาดใหญ่สำหรับสคริปต์เล็กน้อยและไม่คิดว่าจะใช้มันเพื่ออะไร อื่น (เช่นเมื่อรายการไม่ args บรรทัดคำสั่ง) หรือพวกเขาเรียนรู้ แต่จากนั้นลืม - ฉันค้นพบมันอีกครั้งทุกปีหรือสองปี…
อ่านเพิ่มเติม

1
@ เกี่ยวกับฉันคิดว่าfor line in fileinput.input()ไม่ใช่วิธีที่ดีที่สุดในการเลือกในกรณีนี้โดยเฉพาะ: OP ต้องการเชื่อมไฟล์เข้าด้วยกันไม่ได้อ่านทีละบรรทัดซึ่งเป็นกระบวนการที่ใช้เวลานานกว่าในทางทฤษฎี
eyquem

1
@eyquem: ไม่ใช่กระบวนการอีกต่อไปในการดำเนินการ ในขณะที่คุณชี้ให้เห็นว่าโซลูชันบนบรรทัดไม่ได้อ่านทีละตัวอักษร พวกเขาอ่านเป็นชิ้น ๆ และดึงเส้นออกจากบัฟเฟอร์ เวลา I / O จะล้นเวลาการแยกวิเคราะห์อย่างสมบูรณ์ดังนั้นตราบใดที่ผู้ใช้ไม่ได้ทำอะไรโง่ ๆ อย่างน่ากลัวในการบัฟเฟอร์มันจะเร็วมาก (และอาจเร็วกว่าการคาดเดาบัฟเฟอร์ที่ดี ปรับขนาดตัวเองถ้าคุณคิดว่า 10,000 เป็นตัวเลือกที่ดี)
abarnert

1
@abertert ไม่ 10,000 ไม่ใช่ตัวเลือกที่ดี มันเป็นตัวเลือกที่แย่มากเพราะมันไม่ใช่พลังของ 2 และมันมีขนาดที่น่าขันเล็กน้อย ขนาดที่ดีกว่าคือ 2097152 (2 21), 16777216 (2 24) หรือแม้แต่ 134217728 (2 ** 27) ทำไมล่ะ 128 MB ไม่มีอะไรในแรม 4 GB
eyquem

2
โค้ดตัวอย่างค่อนข้างไม่ถูกต้องสำหรับ Python 2.7.10 และใหม่กว่า: stackoverflow.com/questions/30835090/…
CnrL

7

ฉันไม่รู้เกี่ยวกับความสง่างาม แต่มันใช้งานได้:

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

8
คุณสามารถหลีกเลี่ยงการวนซ้ำ: import os; os.system ("cat file * .txt >> OutFile.txt")
lib

7
ไม่ข้ามแพลตฟอร์มและจะทำลายชื่อแฟ้มที่มีช่องว่างในพวกเขา
แกะบิน

4
สิ่งนี้ไม่ปลอดภัย นอกจากนี้ยังcatสามารถใช้รายการไฟล์ดังนั้นจึงไม่จำเป็นต้องเรียกมันซ้ำ ๆ คุณสามารถทำให้ปลอดภัยได้อย่างง่ายดายโดยการโทรsubprocess.check_callแทนos.system
Clément

5

เกิดอะไรขึ้นกับคำสั่ง UNIX (เนื่องจากคุณไม่ได้ทำงานบน Windows):

ls | xargs cat | tee output.txt ทำงานได้อย่างไร (คุณสามารถโทรจาก python ด้วย subprocess ถ้าคุณต้องการ)


21
เพราะนี่เป็นคำถามเกี่ยวกับหลาม
ObscureRobot

2
ที่ไม่ถูกต้องไม่มีอะไรในทั่วไป แต่คำตอบนี้เสีย (ไม่ผ่านการส่งออกของ LS ที่จะ xargs เพียงผ่านรายการของไฟล์กับแมวโดยตรง: cat * | tee output.txt)
Clément

ถ้ามันสามารถใส่ชื่อไฟล์ได้เช่นกันมันคงจะดีมาก
Deqing

@Deqing เพื่อระบุชื่อไฟล์อินพุตคุณสามารถใช้cat file1.txt file2.txt | tee output.txt
GoTrained

1
... และคุณสามารถปิดใช้งานการส่งไปยัง stdout (การพิมพ์ในเทอร์มินัล) โดยการเพิ่ม1> /dev/nullที่ส่วนท้ายของคำสั่ง
GoTrained

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

มาตรฐานที่เรียบง่ายแสดงให้เห็นว่า shutil ทำงานได้ดีขึ้น


3

อีกทางเลือกหนึ่งสำหรับ @ inspectorG4dget คำตอบ (คำตอบที่ดีที่สุดถึงวันที่ 29-03-2016) ฉันทดสอบด้วย 3 ไฟล์ 436MB

@ วิธีแก้ปัญหา inspectorG4dget: 162 วินาที

ทางออกต่อไปนี้: 125 วินาที

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

แนวคิดคือการสร้างไฟล์แบตช์และดำเนินการโดยใช้ประโยชน์จาก "เทคโนโลยีที่ดีเก่า" มันกึ่งงูหลาม แต่ทำงานได้เร็วขึ้น ใช้งานได้กับ windows


2

หากไฟล์ไม่ใหญ่โต:

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

หากไฟล์มีขนาดใหญ่เกินกว่าที่จะอ่านและเก็บไว้ใน RAM ได้อัลกอริทึมจะต้องแตกต่างกันเล็กน้อยในการอ่านแต่ละไฟล์ที่จะคัดลอกในลูปโดยกลุ่มของความยาวคงที่โดยใช้read(10000)ตัวอย่างเช่น


@ Lattyware เพราะฉันค่อนข้างแน่ใจว่าการดำเนินการเร็วขึ้น ในความเป็นจริงแล้วแม้ว่าในขณะที่โค้ดสั่งให้อ่านไฟล์ทีละบรรทัดไฟล์นั้นจะถูกอ่านโดย chunks ซึ่งจะถูกเก็บไว้ในแคชซึ่งแต่ละบรรทัดจะอ่านทีละบรรทัด ขั้นตอนที่ดีกว่าคือการใส่ความยาวของการอ่านอันเท่ากับขนาดของแคช แต่ฉันไม่รู้วิธีกำหนดขนาดของแคชนี้
eyquem

นั่นเป็นการนำไปปฏิบัติใน CPython แต่ไม่มีสิ่งใดรับประกันได้ การปรับให้เหมาะสมเช่นนั้นเป็นความคิดที่ไม่ดีราวกับว่ามันอาจมีประสิทธิภาพในบางระบบ แต่อาจไม่ได้อยู่ที่คนอื่น
Gareth Latty

1
ใช่แน่นอนการอ่านแบบบรรทัดต่อบรรทัดถูกบัฟเฟอร์ นั่นคือเหตุผลที่ทำไมมันไม่ช้ากว่านั้นมาก (ในความเป็นจริงในบางกรณีมันอาจจะเร็วกว่าเล็กน้อยเพราะใครก็ตามที่พอร์ต Python ในแพลตฟอร์มของคุณเลือกขนาดของ chunk ที่ดีกว่า 10,000) ถ้าประสิทธิภาพของเรื่องนี้สำคัญคุณจะต้องทำการปรับใช้ที่แตกต่างกัน แต่ 99.99 …% ของเวลาไม่ว่าทางใดทางหนึ่งจะเร็วเกินพอหรือดิสก์ I / O จริงนั้นเป็นส่วนที่ช้าและไม่สำคัญว่าโค้ดของคุณจะเป็นอย่างไร
abarnert

นอกจากนี้หากคุณต้องการเพิ่มประสิทธิภาพการบัฟเฟอร์ด้วยตนเองอย่างแท้จริงคุณจะต้องใช้os.openและos.readเนื่องจากการopenใช้ตัวห่อของ Python ที่ล้อมรอบ stdio ของ C ซึ่งหมายถึงบัฟเฟอร์เสริม 1 หรือ 2 บัฟเฟอร์เข้ามาขวางทางคุณ
abarnert

ป.ล. สำหรับเหตุผลที่ 10,000 ไม่ดี: ไฟล์ของคุณอาจอยู่บนดิสก์พร้อมบล็อกที่มีความยาวไบต์ สมมุติว่าพวกมันมีขนาด 4096 ไบต์ ดังนั้นการอ่าน 10,000 ไบต์หมายถึงการอ่านสองช่วงตึกจากนั้นเป็นส่วนหนึ่งของถัดไป การอ่านอีก 10,000 หมายถึงการอ่านส่วนที่เหลือของถัดไปจากนั้นสองช่วงตึกจากนั้นเป็นส่วนหนึ่งของส่วนถัดไป นับจำนวนที่คุณอ่านได้เพียงบางส่วนหรือทั้งหมดและคุณเสียเวลามาก โชคดีที่ Python, stdio, ระบบไฟล์และเคอร์เนลบัฟเฟอร์และแคชจะซ่อนปัญหาส่วนใหญ่จากคุณ แต่ทำไมพยายามสร้างมันขึ้นมาตั้งแต่แรก?
abarnert

2

หากคุณมีไฟล์จำนวนมากในไดเรกทอรีglob2อาจเป็นตัวเลือกที่ดีกว่าในการสร้างรายชื่อไฟล์แทนที่จะเขียนด้วยมือ

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

สิ่งนี้เกี่ยวข้องกับคำถามได้อย่างไร ทำไมใช้glob2แทนของglobโมดูลหรือการทำงานใน globbing pathlib?
AMC

1

ตรวจสอบวิธีการ. อ่าน () ของวัตถุไฟล์:

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

คุณสามารถทำสิ่งที่ชอบ:

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

หรือทาง ธ 'หรูหรา':

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

ซึ่งตามบทความนี้: http://www.skymind.com/~ocrow/python_string/ก็จะเร็วที่สุดเช่นกัน


10
สิ่งนี้จะสร้างสตริงยักษ์ซึ่งขึ้นอยู่กับขนาดของไฟล์อาจใหญ่กว่าหน่วยความจำที่มีอยู่ Python ช่วยให้สามารถเข้าถึงไฟล์ได้ง่าย ๆ จึงเป็นความคิดที่ดี
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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.