Текстові файли поєднують Python


168

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

Чи є більш "елегантний" спосіб зробити це в Python?


7
Це не пітон, але в сценаріях оболонок ви можете зробити щось на кшталт cat file1.txt file2.txt file3.txt ... > output.txt. У python, якщо вам це не подобається readline(), є завжди readlines()або просто read().
jedwards

1
@jedwards просто запустіть cat file1.txt file2.txt file3.txtкоманду за допомогою subprocessмодуля, і ви закінчите. Але я не впевнений, чи catпрацює у вікнах.
Ашвіні Шадхарі

5
Як зауваження, спосіб, який ви описуєте, - це жахливий спосіб прочитати файл. Використовуйте withоператор, щоб переконатися, що ваші файли закриті належним чином, і повторіть файл, щоб отримати рядки, а не використовувати f.readline().
Гарет Летті

@jedwards cat не працює, коли текстовий файл є unicode.
Аві Коен

Фактичний аналіз waymoot.org/home/python_string
nu everest

Відповіді:


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
Для великих файлів це буде дуже неефективно.
Гарет Летті

1
@ inspectorG4dget: Я не просив вас, я питав ейкуем, який скаржився, що ваше рішення не буде ефективним. Я готовий зробити ставку, що це більш ніж досить ефективно для випадку використання ОП, і для будь-яких випадків використання на увазі ейкуем. Якщо він вважає, що це не так, це його обов'язок довести, перш ніж вимагати оптимізувати це.
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'):добре, я замінив на оператор для включення всіх файлів у каталог, але мій output_fileпочав рости дуже величезним, як у 100-х ГБ за дуже швидкий час.
R__raki__

10
Зауважте, що є об'єднання останніх рядків кожного файлу з першими рядками наступного файлу, якщо немає символів EOL. У моєму випадку я отримав повністю пошкоджений результат після використання цього коду. Я додав wfd.write (b "\ n") після copyfileobj, щоб отримати нормальний результат
Thelambofgoat

1
@Thelambofgoat я б сказав, що це не чиста конкатенація в цьому випадку, але ей, все, що відповідає вашим потребам.
HelloGoodbye

59

Саме для цього призначений файлinput :

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

@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 * .txt >> OutFile.txt")
lib

6
не буде кросплатформою і перерветься для імен файлів із пробілами в них
літаючі вівці

3
Це небезпечно; також catможе приймати список файлів, тому не потрібно повторно телефонувати. Ви можете легко зробити це безпечним, зателефонувавши subprocess.check_callзамістьos.system
Clément

5

Що не так з командами UNIX? (враховуючи, що ви не працюєте в Windows):

ls | xargs cat | tee output.txt виконує завдання (якщо ви хочете, можна зателефонувати з python з підпроцесом)


21
тому що це питання про python.
ObscureRobot

2
Взагалі нічого поганого, але ця відповідь порушена (не передайте висновок ls до xargs, просто передайте список файлів коту безпосередньо:) cat * | tee output.txt.
Клімент

Якщо він може також вставити ім'я файлу, це було б чудово.
Декін

@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

Простий орієнтир показує, що шутил працює краще.


3

Альтернатива відповіді @ inspectorG4dget (найкраща відповідь на сьогодні 29-03-2016). Я протестував 3 файли розміром 436 Мб.

@ інспекторG4dget рішення: 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()

або більш "елегантний" пітон:

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)наприклад.


@Lattyware Тому що я впевнений, що виконання швидше. До речі, насправді, навіть коли код наказує читати файл рядок за рядком, файл зчитується фрагментами, які вкладаються в кеш, в якому кожен рядок потім читається один за одним. Кращою процедурою було б встановити довжину зчитуваного фрагмента, рівну розміру кешу. Але я не знаю, як визначити розмір кешу.
eyquem

Це реалізація в CPython, але нічого з цього не гарантується. Оптимізація подібного є поганою ідеєю, оскільки, хоча вона може бути ефективною для деяких систем, а не для інших.
Гарет Латті

1
Так, звичайно, рядкове читання буферизоване. Саме тому це не так повільніше. (Насправді, в деяких випадках це може бути навіть трохи швидше, тому що той, хто переніс Python на вашу платформу, вибрав значно кращий розмір, ніж 10000.) Якщо продуктивність цього дійсно має значення, вам доведеться профайлювати різні реалізації. Але 99,99…% часу, будь-який спосіб більш ніж швидкий, або власне дисковий введення / вивід є повільною частиною, і не має значення, що робить ваш код.
abarnert

Крім того, якщо вам дійсно потрібно оптимізувати буферизацію вручну, вам потрібно скористатися os.openі os.read, оскільки звичайна 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()

-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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.