Manchmal ist es einfacher, große Datenmengen nicht auf einmal zu verarbeiten, sondern sie zuerst in kleine Stücke zu zerteilen. Ein Beispiel, das eine Teilung notwendig macht, ist das Umwandeln von XML mit XSLT, das bei sehr großen Dateien zu viel Speicher beansprucht.
Das Unix-Kommando split kann große Textdateien in kleine zerlegen, aber für manche Anwendungsfälle wie die Teilung von sehr großen XML-Dateien ist das nicht ausreichend. Es gelten dabei folgende Anforderungen:
- Die Trennung darf nur an bestimmten Stellen erfolgen (d.h. zumeist hinter bestimmten schließenden XML-Tags).
- Aber nicht an jeder dieser möglichen Trennstellen muss auch tatsächlich getrennt werden.
- Ein Header/Footer (mit äußeren XML-Tags) muss in jedem Teil erhalten bleiben bzw. kopiert werden.
Das split-Kommando ist dafür zu unflexibel, da es entweder nach einer bestimmten Anzahl Zeilen oder an definierten Trennstellen teilt, aber keine Kombination aus beidem zulässt und außerdem die automatische Übernahme von Kopf- und Fußzeilen nicht zulässt.
Folgendes Python-Skript erledigt den Job (getestet mit Python 3.6):
import os,sys,getopt def usage(): print("Usage: zrhkstk.py -i inputfile -h headlines -t taillines -p pattern -n lines") def tail_file(qfile, nlines): qfile.seek(0, os.SEEK_END) endf = position = qfile.tell() linecnt = 0 while position >= 0: qfile.seek(position) next_char = qfile.read(1) if next_char == "\n" and position != endf-1: linecnt += 1 if linecnt == nlines: break position -= 1 if position < 0: qfile.seek(0) return qfile.read(), position try: opts, args = getopt.getopt(sys.argv[1:],"i:h:t:p:n:") except getopt.GetoptError: usage() sys.exit(2) infilename = '' headlines = 1 taillines = 1 pattern = '' nlines = 100000 prefix = 'part' try: for opt, arg in opts: if (opt == '-i'): infilename = arg elif (opt == '-h'): headlines = int(arg) elif (opt == '-t'): taillines = int(arg) elif (opt == '-p'): pattern = arg elif (opt == '-n'): nlines = int(arg) except: usage() sys.exit(2) infile = open(infilename, 'r') tail, tailpos = tail_file(infile, taillines) infile.seek(0,0) head = '' for n in range(0,headlines): line = infile.readline() head = head + line nfile = 1 while True: outfilename = "part"+str(nfile)+"-"+infilename with open(outfilename, 'w') as outfile: outfile.write(head) n = 0 while True: line = infile.readline() if (line==''): # EOF infile.close() outfile.close() sys.exit() outfile.write(line) n = n + 1 if (n > nlines and pattern in line): break outfile.write(tail) nfile = nfile + 1
Wenn z.B. ein riesiges XML-File nur hinter schließenden </record>-Tags getrennt werden darf, frühestens nach 100000 Zeilen getrennt werden soll und 2 Kopf- und 1 Fußzeile in jeden Teil übernommen werden sollen, reicht ein Aufruf von:
python3 zrhkstk.py -i input.xml -p "</record>" -h 2
Die Namen der Ausgabedateien beginnen mit „part“ gefolgt von einer Nummer, einem Trennstrich und anschließend dem Namen der Eingabedatei.
Für die Funktion tail_file, die ähnlich wie das Unix-Kommando tail die letzten n Zeilen einer Datei findet, habe ich mich bei einem Skript bedient, das in einem Kommentar auf github gepostet wurde.