Python łączy pliki tekstowe


168

Mam listę 20 nazw plików, na przykład ['file1.txt', 'file2.txt', ...]. Chcę napisać skrypt w języku Python, aby połączyć te pliki w nowy plik. Mógłbym otworzyć każdy plik przez f = open(...), przeczytać wiersz po wierszu przez wywołanie f.readline()i zapisać każdy wiersz w tym nowym pliku. Nie wydaje mi się to zbyt „eleganckie”, zwłaszcza część, w której muszę czytać // pisać wiersz po wierszu.

Czy jest bardziej „elegancki” sposób na zrobienie tego w Pythonie?


7
To nie jest Python, ale w skryptach powłoki możesz zrobić coś takiego cat file1.txt file2.txt file3.txt ... > output.txt. W Pythonie, jeśli nie lubisz readline(), jest zawsze readlines()lub po prostu read().
później

1
@jedwards po prostu uruchom cat file1.txt file2.txt file3.txtpolecenie za pomocą subprocessmodule i gotowe. Ale nie jestem pewien, czy catdziała w systemie Windows.
Ashwini Chaudhary

5
Uwaga: sposób, w jaki opisujesz, jest okropnym sposobem odczytu pliku. Użyj withinstrukcji, aby upewnić się, że pliki są poprawnie zamknięte, i iteruj po pliku, aby uzyskać wiersze, zamiast używać f.readline().
Gareth Latty

@jedwards cat nie działa, gdy plik tekstowy jest w formacie Unicode.
Avi Cohen

Odpowiedzi:


258

To powinno wystarczyć

W przypadku dużych plików:

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)

W przypadku małych plików:

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

… I kolejny ciekawy, o którym pomyślałem :

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)

Niestety, ta ostatnia metoda pozostawia kilka otwartych deskryptorów plików, którymi i tak powinien się zająć GC. Po prostu pomyślałem, że to interesujące


9
W przypadku dużych plików będzie to bardzo nieefektywne pod względem pamięci.
Gareth Latty

1
@ inspectorG4dget: Nie pytałem cię, pytałem eyquema, który narzekał, że twoje rozwiązanie nie będzie skuteczne. Jestem skłonny założyć się, że jest to więcej niż wystarczająco wydajne dla przypadku użycia OP i dla każdego przypadku użycia, który eyquem ma na myśli. Jeśli uważa, że ​​tak nie jest, jego obowiązkiem jest udowodnienie tego, zanim zażąda optymalizacji.
abarnert

2
co uważamy za duży plik?
Dee

4
@dee: plik tak duży, że jego zawartość nie mieści się w pamięci głównej
inspectorG4dget

7
Powtórzę tylko: to jest zła odpowiedź, shutil.copyfileobj jest właściwą odpowiedzią.
Paul Crowley,

193

Użyj shutil.copyfileobj.

Automatycznie czyta pliki wejściowe fragment po kawałku dla Ciebie, co jest bardziej wydajne i wczytuje pliki wejściowe i będzie działać, nawet jeśli niektóre pliki wejściowe są zbyt duże, aby zmieścić się w pamięci:

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'):cóż, zastąpiłem instrukcję for, aby uwzględnić wszystkie pliki w katalogu, ale output_filezacząłem rosnąć naprawdę bardzo, jak w setkach GB w bardzo krótkim czasie.
R__raki__

10
Zauważ, że to oznacza scalenie ostatnich ciągów każdego pliku z pierwszymi ciągami następnego pliku, jeśli nie ma znaków EOL. W moim przypadku otrzymałem całkowicie uszkodzony wynik po użyciu tego kodu. Dodałem wfd.write (b "\ n") po copyfileobj, aby uzyskać normalny wynik
Thelambofgoat

1
@Thelambofgoat Powiedziałbym, że w tym przypadku nie jest to czysta konkatenacja, ale hej, cokolwiek odpowiada Twoim potrzebom.
HelloGoodbye

59

Właśnie do tego służy fileinput :

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

W tym przypadku nie jest to o wiele prostsze niż tylko ręczne iterowanie po plikach, ale w innych przypadkach posiadanie jednego iteratora, który iteruje wszystkie pliki, tak jakby były jednym plikiem, jest bardzo przydatne. (Ponadto fakt, że fileinputzamyka każdy plik zaraz po zakończeniu, oznacza, że ​​nie ma takiej potrzeby withlub closekażdego z nich, ale to tylko jedna linia oszczędności, a nie taka wielka sprawa).

W programie jest kilka innych przydatnych funkcji fileinput, takich jak możliwość wykonywania lokalnych modyfikacji plików, po prostu filtrując każdą linię.


Jak zauważono w komentarzach i omówiono w innym poście , fileinputPython 2.7 nie będzie działał zgodnie z opisem. Tutaj niewielka modyfikacja, aby kod był zgodny z Pythonem 2.7

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

@ Lattyware: Myślę, że większość ludzi, którzy się o tym uczą, słyszy fileinput, że jest to sposób na przekształcenie prostego sys.argv(lub tego, co zostało jako argumenty po optparse/ itp.) W duży wirtualny plik dla trywialnych skryptów i nie myśli o używaniu go do niczego else (tj. gdy lista nie jest argumentami wiersza poleceń). Albo uczą się, ale potem zapominają - odkrywam to na nowo co rok lub dwa…
abarnert

1
@abament Myślę, że for line in fileinput.input()nie jest to najlepszy sposób wyboru w tym konkretnym przypadku: OP chce łączyć pliki, a nie czytać je wiersz po wierszu, co jest teoretycznie dłuższym procesem do wykonania
eyquem

1
@eyquem: To nie jest dłuższy proces do wykonania. Jak sam zauważyłeś, rozwiązania oparte na wierszach nie czytają jednego znaku na raz; czytają fragmentami i wyciągają wiersze z bufora. Czas I / O całkowicie zapełni czas analizy linii, tak długo, jak implementator nie zrobił czegoś strasznie głupiego w buforowaniu, będzie równie szybki (i prawdopodobnie nawet szybszy niż próba odgadnięcia dobrego bufora zmień swój rozmiar, jeśli uważasz, że 10000 to dobry wybór).
abarnert

1
@abarnert NIE, 10000 nie jest dobrym wyborem. To naprawdę bardzo zły wybór, ponieważ nie jest to potęga 2 i jest śmiesznie mały. Lepsze rozmiary to 2097152 (2 21), 16777216 (2 24) lub nawet 134217728 (2 ** 27), czemu nie? 128 MB to nic w pamięci RAM o pojemności 4 GB.
eyquem

2
Przykładowy kod nie do końca poprawny dla Pythona 2.7.10 i nowszych: stackoverflow.com/questions/30835090/ ...
CnrL

8

Nie znam elegancji, ale to działa:

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

8
możesz nawet uniknąć pętli: import os; os.system ("cat file * .txt >> OutFile.txt")
lib

6
nie wieloplatformowy i będzie się łamał dla nazw plików ze spacjami
latające owce

3
To jest niepewne; również catmoże wziąć listę plików, więc nie ma potrzeby wielokrotnego wywoływania tego. Możesz łatwo to zabezpieczyć dzwoniąc subprocess.check_callzamiastos.system
Clément

5

Co jest nie tak z poleceniami UNIX? (zakładając, że nie pracujesz w systemie Windows):

ls | xargs cat | tee output.txt wykonuje zadanie (jeśli chcesz, możesz to wywołać z Pythona z podprocesem)


21
ponieważ jest to pytanie dotyczące Pythona.
ObscureRobot

2
Ogólnie nic złego, ale ta odpowiedź jest zepsuta (nie przekazuj wyjścia ls do xargs, po prostu prześlij listę plików bezpośrednio do cat:) cat * | tee output.txt.
Clément

Byłoby świetnie, gdyby mógł również wstawić nazwę pliku.
Deqing

@Deqing Aby określić nazwy plików wejściowych, możesz użyćcat file1.txt file2.txt | tee output.txt
GoTrained

1
... i możesz wyłączyć wysyłanie na stdout (drukowanie w terminalu), dodając 1> /dev/nullna końcu polecenia
GoTrained

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

Prosty test porównawczy pokazuje, że shutil działa lepiej.


3

Alternatywa dla odpowiedzi @ inspectorG4dget (najlepsza odpowiedź do tej pory 29-03-2016). Testowałem z 3 plikami po 436 MB.

@ inspectorG4dget rozwiązanie: 162 sekundy

Następujące rozwiązanie: 125 sekund

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

Chodzi o to, aby utworzyć plik wsadowy i uruchomić go, wykorzystując „starą dobrą technologię”. Jego semi-Python, ale działa szybciej. Działa dla okien.


3

Jeśli masz dużo plików w katalogu, glob2lepszym rozwiązaniem może być wygenerowanie listy nazw plików, zamiast wpisywania ich ręcznie.

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

Sprawdź metodę .read () obiektu File:

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

Możesz zrobić coś takiego:

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

lub bardziej „elegancki” sposób w Pythonie:

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

co zgodnie z tym artykułem: http://www.skymind.com/~ocrow/python_string/ byłoby również najszybsze.


10
W ten sposób powstanie gigantyczny ciąg, który w zależności od rozmiaru plików może być większy niż dostępna pamięć. Ponieważ Python zapewnia łatwy, leniwy dostęp do plików, jest to zły pomysł.
Gareth Latty

2

Jeśli pliki nie są gigantyczne:

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

Jeśli pliki są zbyt duże, aby można je było w całości odczytać i przechowywać w pamięci RAM, algorytm musi być nieco inny, aby odczytać każdy plik, który ma być skopiowany w pętli przez fragmenty o ustalonej długości, read(10000)na przykład.


@Lattyware Ponieważ jestem pewien, że wykonanie jest szybsze. Nawiasem mówiąc, nawet jeśli kod nakazuje odczytanie pliku wiersz po wierszu, plik jest odczytywany fragmentami, które są umieszczane w pamięci podręcznej, w której każda linia jest następnie odczytywana jedna po drugiej. Lepszą procedurą byłoby ustawienie długości fragmentu odczytu równej rozmiarowi pamięci podręcznej. Ale nie wiem, jak określić rozmiar tej pamięci podręcznej.
eyquem

Taka jest implementacja w CPythonie, ale nic z tego nie jest gwarantowane. Taka optymalizacja jest złym pomysłem, ponieważ może być skuteczna w niektórych systemach, ale może nie działać w innych.
Gareth Latty

1
Tak, oczywiście odczyt wiersz po wierszu jest buforowany. Właśnie dlatego nie jest dużo wolniejszy. (W rzeczywistości w niektórych przypadkach może to być nawet nieco szybsze, ponieważ ktokolwiek przeniósł Pythona na twoją platformę, wybrał znacznie lepszy rozmiar fragmentu niż 10000). Jeśli wydajność to naprawdę ma znaczenie, będziesz musiał profilować różne implementacje. Ale 99,99…% czasu, tak czy inaczej, jest wystarczająco szybkie lub faktyczne operacje we / wy dysku są wolniejsze i nie ma znaczenia, co robi twój kod.
abarnert

Ponadto, jeśli naprawdę potrzebujesz ręcznie zoptymalizować buforowanie, będziesz chciał użyć os.openi os.read, ponieważ zwykły openużywa opakowań Pythona wokół stdio C, co oznacza 1 lub 2 dodatkowe bufory wchodzące ci w drogę.
abarnert

PS, dlaczego 10000 jest złe: Twoje pliki są prawdopodobnie na dysku, z blokami, które mają pewną moc bajtów. Powiedzmy, że mają 4096 bajtów. Zatem odczyt 10000 bajtów oznacza odczytanie dwóch bloków, a następnie części następnego. Przeczytanie kolejnych 10000 oznacza przeczytanie reszty następnego, potem dwóch bloków, a następnie części następnego. Policz, ile masz częściowych lub pełnych odczytów bloków, a tracisz dużo czasu. Na szczęście buforowanie i buforowanie Pythona, stdio, systemu plików i jądra ukryje przed Tobą większość tych problemów, ale po co w ogóle próbować je tworzyć?
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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.