Python конкатенация текстовых файлов

168

У меня есть список из 20 имен файлов, например ['file1.txt', 'file2.txt', ...]. Я хочу написать скрипт Python для объединения этих файлов в новый файл. Я мог бы открыть каждый файл f = open(...), прочитать строку за строкой, позвонив f.readline(), и записать каждую строку в этот новый файл. Это не кажется мне очень «элегантным», особенно та часть, где я должен читать // писать построчно.

Есть ли более «элегантный» способ сделать это в Python?

Джей Джей Бек
источник
7
Это не Python, но в сценариях оболочки вы можете сделать что-то вроде cat file1.txt file2.txt file3.txt ... > output.txt. В питоне, если вам не нравится readline(), всегда есть readlines()или просто read().
Джедвардс
1
@jedwards просто запустите cat file1.txt file2.txt file3.txtкоманду, используя subprocessмодуль, и все готово. Но я не уверен, catработает ли в Windows.
Ашвини Чаудхари
5
Как примечание, способ, которым вы описываете, является ужасным способом прочитать файл. Используйте withоператор, чтобы убедиться, что ваши файлы закрыты должным образом, и выполняйте итерации по файлу, чтобы получить строки, а не используйте f.readline().
Гарет Латти
@jedwards cat не работает, когда текстовый файл в Unicode.
Ави Коэн
Актуальный анализ 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 должен позаботиться в любом случае. Я просто подумал, что это интересно

inspectorG4dget
источник
9
Для больших файлов это будет очень неэффективно для памяти.
Гарет Латти
1
@ inspectorG4dget: Я не спрашивал вас, я спрашивал Айкума, который жаловался, что ваше решение не будет эффективным. Я готов поспорить, что он более чем достаточно эффективен для сценария использования OP и для любого варианта использования, который имеет в виду eyquem. Если он считает, что это не так, он обязан доказать это, прежде чем требовать, чтобы вы оптимизировали его.
abarnert
2
чем мы считаем большой файл?
Ди
4
@dee: файл настолько велик , что его содержание не вписывается в основную память
inspectorG4dget
7
Просто повторюсь: это неправильный ответ, shutil.copyfileobj - правильный ответ.
Пол Кроули
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рост стал действительно огромным, как в сотнях гб за очень короткое время
R__raki__
10
Обратите внимание, что это объединит последние строки каждого файла с первыми строками следующего файла, если нет символов EOL. В моем случае я получил полностью испорченный результат после использования этого кода. Я добавил wfd.write (b "\ n") после copyfileobj, чтобы получить нормальный результат
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каждый файл закрывается сразу после его завершения, означает, что в этом нет необходимости withили closeкаждый из них, но это всего лишь экономия в одну строку, а не такая уж большая проблема.)

Есть и другие отличные функции fileinput, такие как возможность вносить изменения в файлы, просто фильтруя каждую строку.


Как отмечено в комментариях и обсуждено в другом посте , fileinputдля Python 2.7 не будет работать, как указано. Здесь небольшое изменение, чтобы сделать код Python 2.7 совместимым

with open('outfilename', 'w') as fout:
    fin = fileinput.input(filenames)
    for line in fin:
        fout.write(line)
    fin.close()
abarnert
источник
@ Lattyware: я думаю, что большинству людей, которые узнают об fileinputэтом, говорят, что это способ превратить простой sys.argv(или то, что осталось в качестве аргументов после optparse/ и т. иначе (то есть, когда список не является аргументами командной строки). Или они действительно учатся, но потом забывают - я продолжаю открывать это каждый год или два…
abarnert
1
@abament Я думаю, что for line in fileinput.input()это не лучший способ выбора в данном конкретном случае: ОП хочет объединять файлы, а не читать их построчно, что теоретически более длительный процесс для выполнения
eyquem
1
@eyquem: это не более длительный процесс для выполнения. Как вы сами отметили, линейные решения не читают по одному символу за раз; они читают порциями и вытаскивают строки из буфера. Время ввода-вывода полностью сократит время разбора строки, так что, пока разработчик не сделал что-то ужасно глупое в буферизации, оно будет таким же быстрым (и, возможно, даже быстрее, чем пытаться угадать хороший буфер) Оцените себя, если вы считаете 10000 хорошим выбором).
abarnert
1
@abarnert НЕТ, 10000 - плохой выбор. Это действительно очень плохой выбор, потому что это не степень 2, и это смехотворно маленький размер. Лучшие размеры будут 2097152 (2 21), 16777216 (2 24) или даже 134217728 (2 ** 27), почему бы и нет, 128 МБ - это ничто в ОЗУ объемом 4 ГБ.
eyquem
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
Clément
5

Что не так с командами UNIX? (учитывая, что вы не работаете в Windows):

ls | xargs cat | tee output.txt делает работу (вы можете вызвать его из python с подпроцессом, если хотите)

lucasg
источник
21
потому что это вопрос о питоне.
ObscureRobot
2
В общем, ничего плохого, но этот ответ не работает (не передавайте вывод ls в xargs, просто передавайте список файлов непосредственно в cat:) cat * | tee output.txt.
Клеман
Если он может вставить имя файла, это было бы здорово.
Deqing
@Deqing Чтобы указать имена входных файлов, вы можете использоватьcat file1.txt file2.txt | tee output.txt
GoTrained
1
... и вы можете отключить отправку на стандартный вывод (печать в терминале), добавив 1> /dev/nullв конец команды
GoTrained
4
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

Простой тест показывает, что шутил работает лучше.

Haoming
источник
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()

Идея состоит в том, чтобы создать пакетный файл и выполнить его, используя преимущества «старой доброй технологии». Его полупитон, но работает быстрее. Работает для окон.

Жоао Пальма
источник
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

Проверьте метод .read () объекта File:

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

Вы могли бы сделать что-то вроде:

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

или более «элегантный» путь Python:

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

который, согласно этой статье: http://www.skymind.com/~ocrow/python_string/ также будет самым быстрым.

Алекс Кавриков
источник
10
Это создаст гигантскую строку, которая в зависимости от размера файлов может быть больше доступной памяти. Поскольку Python обеспечивает легкий ленивый доступ к файлам, это плохая идея.
Гарет Латти
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

Если файлы слишком велики, чтобы их можно было целиком прочитать и хранить в оперативной памяти, алгоритм должен немного отличаться, чтобы каждый файл, который будет скопирован в цикле, был прочитан фрагментами фиксированной длины, read(10000)например , с помощью .

Eyquem
источник
@ Lattyware Потому что я уверен, что выполнение будет быстрее. Кстати, фактически, даже когда код приказывает читать файл построчно, файл читается кусками, которые помещаются в кеш, в котором каждая строка затем читается одна за другой. Лучшей процедурой было бы установить длину блока чтения равной размеру кэша. Но я не знаю, как определить размер этого кэша.
eyquem
Это реализация в CPython, но ничего из этого не гарантировано. Подобная оптимизация является плохой идеей, поскольку в некоторых системах она может быть эффективной, а в других - нет.
Гарет Латти
1
Да, конечно, построчное чтение буферизуется. Именно поэтому это не намного медленнее. (На самом деле, в некоторых случаях это может даже быть немного быстрее, потому что тот, кто перенес Python на вашу платформу, выбрал гораздо лучший размер куска, чем 10000.) Если производительность этого действительно имеет значение, вам придется профилировать различные реализации. Но 99,99% времени, в любом случае, более чем достаточно, или реальный дисковый ввод-вывод - медленная часть, и не имеет значения, что делает ваш код.
abarnert
Кроме того, если вам действительно нужно вручную оптимизировать буферизацию, вы захотите использовать os.openи os.read, поскольку plain openиспользует обертки Python вокруг stdio C, что означает, что на вашем пути встанет 1 или 2 дополнительных буфера.
abarnert
PS, почему 10000 плохо: ваши файлы, вероятно, находятся на диске, с блоками, длина которых составляет несколько байтов. Допустим, они 4096 байтов. Таким образом, чтение 10000 байтов означает чтение двух блоков, а затем части следующего. Чтение еще 10000 означает чтение остальной части следующего, затем двух блоков, а затем части следующего. Подсчитайте, сколько частичных или полных чтений блоков у вас есть, и вы тратите много времени. К счастью, буферизация и кэширование Python, stdio, файловой системы и ядра скрывают большинство этих проблем от вас, но зачем пытаться создавать их в первую очередь?
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()
user2825287
источник
-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
источник