Wenn die Dateien identische Spalten in identischer Reihenfolge haben, ist der schnellste korrekte Weg ein Shell-Einzeiler, der den ersten Header behält und alle anderen weglässt:
head -n 1 first.csv > merged.csv
tail -q -n +2 *.csv >> merged.csv
Alles andere auf dieser Seite gibt es, weil die Annahme identischer Spalten in identischer Reihenfolge viel öfter nicht stimmt, als man denkt, und der Fehler bleibt unbemerkt. Ein Merge, der zwei Spalten verschiebt, liefert eine Datei, die sich problemlos öffnen lässt, gut aussieht und trotzdem falsch ist.
Zuerst prüfen, ob die Header übereinstimmen
Mach das vor dem Zusammenführen, nicht danach. Jedes weitere Problem ist eine Variante dieses einen.
for f in *.csv; do echo "$f: $(head -n 1 "$f")"; done
Achte in der Ausgabe auf vier Dinge:
- Andere Spaltenreihenfolge.
name,email,datein der einen Datei undname,date,emailin der anderen. Beim Aneinanderhängen landen Datumswerte in der E-Mail-Spalte, und nichts beschwert sich. - Verschiedene Namen für dasselbe Feld.
Email,email,E-mail,Email Address. Typisch, wenn die Dateien aus verschiedenen Exporten stammen oder derselbe Export in verschiedenen Jahren gemacht wurde. - Zusätzliche oder fehlende Spalten. Ein späterer Export hat ein Feld dazubekommen. Die Zeilen sind jetzt unterschiedlich breit.
- Ein Byte Order Mark (BOM) in der ersten Datei.
§BOM§nameist nichtname. Es ist in jedem Editor unsichtbar und bricht Joins über Spaltennamen.
Sind alle Header byteidentisch, nimm die Zweizeilen-Variante oben und hör auf zu lesen. Wenn nicht, ist Aneinanderhängen das falsche Werkzeug, und du brauchst etwas, das nach Spaltenname zusammenführt.
Nach Spaltenname zusammenführen
DuckDB, wenn die Dateien groß sind oder die Header abweichen
Ein Befehl löst beide Probleme, abweichende Spaltenreihenfolge und fehlende Spalten, weil er nach Name statt nach Position ausrichtet:
duckdb -c "COPY (SELECT * FROM read_csv('*.csv', union_by_name=true, filename=true)) TO 'merged.csv' (HEADER)"
union_by_name=true leistet die eigentliche Arbeit: Spalten werden über den
Header zugeordnet, und eine Datei ohne eine bestimmte Spalte bekommt dafür
NULL, statt dass die Zeile verrutscht. filename=true fügt eine Spalte hinzu,
die festhält, aus welcher Datei jede Zeile stammt. Die wirst du öfter brauchen,
als du denkst. Nur so lässt sich im Nachhinein beantworten, in welchem Export
eine fehlerhafte Zeile angekommen ist.
DuckDB streamt, die Dateigröße wird also durch die Festplatte begrenzt, nicht durch den Arbeitsspeicher. Hundert Dateien mit zusammen mehreren Gigabyte sind nichts Besonderes.
Python, wenn du die Namen erst angleichen musst
Wenn dasselbe Feld in einer Datei Email und in einer anderen E-mail Address
heißt, kann kein Werkzeug das erraten. Vereinheitliche ausdrücklich:
import glob, pandas as pd
rename = {"E-mail Address": "email", "Email": "email"}
frames = []
for path in glob.glob("*.csv"):
df = pd.read_csv(path, dtype=str)
df.columns = [c.strip().lstrip("§BOM§") for c in df.columns]
frames.append(df.rename(columns=rename).assign(source=path))
pd.concat(frames, ignore_index=True).to_csv("merged.csv", index=False)
Zwei Details sind wichtiger als der Merge selbst. dtype=str verhindert, dass
pandas die Typen pro Datei errät. Ohne das kommt eine Kunden-ID-Spalte, die in
einer Datei numerisch und in einer anderen alphanumerisch ist, als Mischung aus
1024 und "01024" heraus, und die führenden Nullen sind für immer weg. Und
.assign(source=path) ist dieselbe Herkunftsspalte, die DuckDB dir geschenkt
liefert.
Power Query, auf dem Desktop ohne Terminal
In Excel zeigt Daten → Daten abrufen → Aus Ordner auf ein Verzeichnis und kombiniert alle Dateien darin, zugeordnet über den Header. Das kann es gut, es aktualisiert sich, wenn neue Dateien im Ordner landen, und es ist die richtige Antwort für alle, die diesen Merge jeden Monat wiederholen.
Seine Grenze ist die Grenze von Excel: Das Ergebnis muss in 1.048.576 Zeilen passen, um auf einem Blatt zu landen. Du kannst stattdessen ins Datenmodell laden und das Blatt ganz auslassen, was die meisten nicht wissen und was die Grenze deutlich anhebt.
Anhängen oder verknüpfen
„Zusammenführen“ meint zwei verschiedene Operationen, und mit der falschen verschwendest du einen Nachmittag.
| Du hast | Du willst | Operation |
|---|---|---|
| Zwölf Monatsexporte, gleiche Spalten | Eine Datei, mehr Zeilen | Anhängen (Append, stapeln) |
| Kunden in einer Datei, Bestellungen in einer anderen | Eine Datei, mehr Spalten | Join (über einen Schlüssel verknüpfen) |
Alles oben ist Anhängen. Ein Join braucht einen gemeinsamen Schlüssel und eine Entscheidung, was mit Zeilen ohne Treffer passiert, und genau hier entstehen Duplikate: Ein Schlüssel, der auf beiden Seiten nicht eindeutig ist, vervielfacht Zeilen unbemerkt. Wenn die Zeilenzahl nach einem Join größer ist als bei beiden Eingaben, ist das passiert.
Für einen Join ist SQL das ehrliche Werkzeug:
duckdb -c "COPY (SELECT c.*, o.order_total FROM read_csv('customers.csv') c LEFT JOIN read_csv('orders.csv') o USING (customer_id)) TO 'merged.csv' (HEADER)"
Auf dem Smartphone
Das realistische Smartphone-Szenario: eine Handvoll Dateien in Mail oder Dateien, drei regionale Exporte oder ein paar Monate Kontoauszüge, und bis Montag kein Laptop.
Zwei Dinge helfen hier. Dateien mit identischen Headern anzuhängen ist Kopieren und Einfügen, vorausgesetzt die App kann einen Zeilenblock einfügen statt nur eine Zelle nach der anderen. Und die wichtige Prüfung, der Header-Vergleich, geht am Smartphone schneller als irgendwo sonst: jede Datei öffnen, die oberste Zeile lesen.
Öffnet jeden Export in Sekunden, fügt einen Zeilenbereich in ein anderes Blatt ein und schreibt beim Speichern jede Zeile zurück, auch in der kostenlosen Version. Dank Spalten- und Zeilen-Einfügen ist es echtes Bearbeiten statt Notlösung, eine Datei abzugleichen, die ein Feld dazubekommen hat.
Was es kannBei mehr als einer Handvoll Dateien warte auf einen Desktop. Dreißig Exporte von Hand auf einem Touchscreen zusammenzuführen ist keine gute Idee, und der Grund ist die Fehlerquote, nicht die Mühe.
Nach dem Merge: vier Prüfungen
Mach sie jedes Mal. Sie dauern eine Minute und fangen praktisch jeden relevanten Merge-Fehler ab.
- Zeilenanzahl. Die Summe im Ergebnis sollte der Summe aller Eingaben
entsprechen, minus ein Header pro Datei.
wc -l *.csvvorher,wc -l merged.csvnachher. - Header-Anzahl.
grep -c "^name," merged.csvoder wie auch immer die erste Spalte heißt. Mehr als einer bedeutet, dass ein Header als Datenzeile gelandet ist. - Spaltenbreite. Jede Zeile sollte gleich viele Felder haben. Ungleiche Breiten deuten auf ein nicht maskiertes Komma hin, nicht auf einen Merge-Fehler.
- Die Nahtstelle prüfen. Schau dir im Ergebnis die letzte Zeile der ersten und die erste Zeile der zweiten Datei an. Verschiebungen fallen dort zuerst auf.
Die zweite Prüfung hat eine Feinheit: Landet eine Header-Zeile in den Daten, verteilt sie das Sortieren mitten in die Datei, und später ist sie sehr schwer zu finden. Fang das jetzt ab.
Führt SQL direkt auf der zusammengeführten Datei aus. Ein GROUP BY auf die Quellspalte zeigt dir, dass jede Datei so viele Zeilen beigetragen hat wie erwartet, die ganze Verifikation in einer Abfrage.
Was es kannKodierung, der Fehler, der jede Prüfung überlebt
Dateien aus verschiedenen Systemen haben oft verschiedene Kodierungen, und
Aneinanderhängen konvertiert nichts. Eine UTF-8-Datei, die an eine
Windows-1252-Datei angehängt wird, ergibt eine Datei, die keins von beiden ist:
Namen sehen in der ersten Hälfte richtig aus und in der zweiten als é.
Konvertiere vor dem Zusammenführen, nicht danach:
iconv -f WINDOWS-1252 -t UTF-8 old.csv > old-utf8.csv
file -I *.csv rät, was du vor dir hast, liegt bei kurzen Dateien aber oft
daneben. Verlässlich ist nur, jede Datei zu öffnen und eine Zeile mit einem
Nicht-ASCII-Zeichen anzusehen.