Python连接文本文件


168

我列出了20个文件名,例如['file1.txt', 'file2.txt', ...]。我想编写一个Python脚本将这些文件连接成一个新文件。我可以通过打开每个文件f = open(...),通过调用逐行读取f.readline(),然后将每一行写入该新文件。在我看来,这并不是很“优雅”,尤其是我必须逐行读取/写入的部分。

在Python中是否有更“优雅”的方式来做到这一点?


7
它不是python,但是在shell脚本中您可以执行cat file1.txt file2.txt file3.txt ... > output.txt。在python中,如果您不喜欢readline(),总是readlines()或简单read()
jedwards 2012年

1
@jedwards只需cat file1.txt file2.txt file3.txt使用subprocess模块运行命令即可完成。但我不确定是否cat可以在Windows中使用。
Ashwini Chaudhary

5
请注意,您描述的方式是一种读取文件的糟糕方式。使用with语句来确保文件正确关闭,并遍历文件以获取行,而不是使用f.readline()
Gareth Latty

当文本文件为unicode时,@ jedwards cat不起作用。
阿维·科恩

Answers:


258

这应该做

对于大文件:

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 2012年

2
我们认为文件是什么?

4
@dee:文件太大以至于其内容无法放入主存储器中
inspectorG4dget

7
重申一下:这是错误的答案,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'):好吧,我替换了for语句以包含目录中的所有文件,但是我output_file很快就开始变得非常庞大,例如100的gb。
R__raki__

10
注意,如果没有EOL字符,它将合并每个文件的最后一个字符串和下一个文件的第一个字符串。就我而言,使用此代码后,结果完全损坏。我在copyfileobj之后添加了wfd.write(b“ \ n”)以得到正常的结果
Thelambofgoat

1
@Thelambofgoat在这种情况下,我想说这不是一个纯粹的串联,但是,嘿,无论您喜欢什么。
HelloGoodbye

59

这正是fileinput的目的:

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

对于这种用例,它不仅比手动遍历文件简单得多,而且在其他情况下,拥有一个遍历所有文件就好像它们是单个文件一样的单个迭代器非常方便。(此外,事实是fileinput,一旦完成就关闭每个文件,这意味着不需要withclose每个文件,但这只是节省一行,而不是什么大不了的。)

中还有其他一些漂亮的功能fileinput,例如仅通过过滤每一行就可以对文件进行就地修改的功能。


正如评论中所述,并在另一篇文章中讨论的那样,fileinputPython 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(或optparse/ etc 之后的args剩下的)转换成大的虚拟文件用于普通脚本的方法,并且不认为将其用于任何用途其他(即,当列表不是命令行参数时)。或者他们确实学习了,但是却忘了-我每年一两年不断地重新发现它……
abarnert 2012年

1
@abament我认为for line in fileinput.input()在这种特殊情况下不是最好的选择:OP希望连接文件,而不是逐行读取文件,这从理论上讲是一个执行时间更长的过程
eyquem 2012年

1
@eyquem:这不是一个更长的执行过程。正如您自己所指出的那样,基于行的解决方案一次不会读取一个字符。他们读取大块并将行从缓冲区中拉出。I / O时间将完全淹没行解析时间,因此,只要实现者没有在缓冲中做一些非常愚蠢的事情,它的速度就会一样快(甚至比尝试猜测一个好的缓冲区还要快)如果您认为10000是一个不错的选择,请自行调整大小)。
abarnert 2012年

1
@abarnert不,10000不是一个好选择。这确实是一个非常糟糕的选择,因为它不是2的幂,而且有点小。更好的大小是2097152(2 21),16777216(2 24)或134217728(2 ** 27),为什么不呢?128 MB在4 GB的RAM中什么也没有。
eyquem 2012年

2
示例代码并没有有效的Python 2.7.10及更高版本:stackoverflow.com/questions/30835090/...
CNRL

8

我对优雅并不了解,但这可行:

    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

6
不会跨平台,并且会破坏其中带有空格的文件名
会飞的羊

3
这是不安全的;此外,cat还可以获取文件列表,因此无需重复调用它。你可以很容易使其安全通过调用subprocess.check_call代替os.system
克莱门特

5

UNIX命令怎么了?(假设您不在Windows上工作):

ls | xargs cat | tee output.txt 做这项工作(如果需要,您可以使用子进程从python调用它)


21
因为这是关于python的问题。
ObscureRobot 2015年

2
一般而言,没有任何问题,但是这个答案已被打破(不要将ls的输出传递给xargs,只需将文件列表直接传递给cat即可:)cat * | tee output.txt
克莱门特

如果它也可以插入文件名,那就太好了。
德庆

@Deqing要指定输入文件名,您可以使用cat file1.txt file2.txt | tee output.txt
GoTrained

1
...,您可以通过添加1> /dev/null到命令末尾来禁用发送到stdout(在终端中打印)的功能
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)。我测试了436MB的3个文件。

@ 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。


3

如果目录中有很多文件,则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')

2

检出File对象的.read()方法:

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

您可以执行以下操作:

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

或更“优雅”的python-way:

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

根据这篇文章:http : //www.skymind.com/~ocrow/python_string/也是最快的。


10
这将产生一个巨大的字符串,根据文件的大小,该字符串可能大于可用内存。由于Python提供了对文件的轻松懒惰访问,所以这不是一个好主意。
Gareth Latty

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因为我非常确定执行速度更快。顺便说一句,实际上,即使代码命令逐行读取文件,文件也会按块读取,这些块被放入缓存中,然后逐行读取每一行。更好的过程是使读取块的长度等于缓存的大小。但我不知道如何确定此缓存的大小。
eyquem 2012年

这是CPython中的实现,但不能保证所有这些。这样的优化是一个坏主意,因为虽然它在某些系统上可能有效,但在其他系统上却无效。
Gareth Latty

1
是的,当然逐行读取是被缓冲的。这就是为什么它没有那么慢的原因。(实际上,在某些情况下,它甚至可能会稍快一些,因为将Python移植到您的平台上的人选择的块大小要比10000小得多。)如果这确实很重要,则必须描述不同的实现。但是99.99…%的时间,无论哪种方式都不够快,或者实际的磁盘I / O是速度较慢的部分,并且您的代码做什么无关紧要。
abarnert 2012年

另外,如果确实需要手动优化缓冲,则需要使用os.openos.read,因为Plain open在C的stdio周围使用了Python的包装器,这意味着有1个或2个额外的缓冲会妨碍您。
abarnert 2012年

PS,关于10000错误的原因:您的文件可能位于磁盘上,并且块的长度为字节数。假设它们是4096个字节。因此,读取10000字节意味着读取两个块,然后读取下一个块。读取另外的10000表示读取剩余的下一个,然后读取两个块,然后读取下一个的一部分。算一下您有多少个部分或全部块读取,您在浪费很多时间。幸运的是,Python,stdio,文件系统以及内核缓冲和缓存将对您隐藏大多数这些问题,但是为什么要首先创建它们呢?
abarnert 2012年

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.