Das join Kommando bringt relationale Logik in die Shell: Es verbindet zwei Dateien anhand eines gemeinsamen Felds — vergleichbar mit einem SQL JOIN. Wo paste Spalten stur nebeneinander legt, sucht join Übereinstimmungen und kombiniert nur passende Zeilen. Voraussetzung ist allerdings, dass beide Dateien nach dem Join-Feld sortiert sind.
Was join macht
join liest zwei Dateien parallel und gibt für jede Zeile, deren Schlüsselfeld in beiden Dateien vorkommt, eine kombinierte Zeile aus. Standardmäßig wird das erste Feld als Schlüssel verwendet, getrennt durch Whitespace.
join users.txt orders.txt1 Anna 250
2 Bernd 95
3 Clara 410Konzeptuell entspricht das einem INNER JOIN in SQL: Nur Zeilen mit Match in beiden Dateien tauchen im Ergebnis auf. Datensätze ohne Gegenstück werden stillschweigend verworfen — es sei denn, du verlangst mit -a oder -v etwas anderes.
Wichtig: Sortierung ist Pflicht
Die Sortierreihenfolge muss exakt dieselbe sein wie die, die join intern verwendet. Standardmäßig ist das die lexikalische Reihenfolge des aktuellen Locale — was in deutschen Locales zu Überraschungen führen kann (z. B. weil ä mit a zusammensortiert wird). Für absolute Sicherheit empfiehlt sich LC_ALL=C sowohl beim sort als auch beim join:
LC_ALL=C sort users.txt -o users.txt
LC_ALL=C sort orders.txt -o orders.txt
LC_ALL=C join users.txt orders.txtOptionen
| Option | Bedeutung |
|---|---|
-1 N | Feldnummer in der ersten Datei (Standard: 1) |
-2 N | Feldnummer in der zweiten Datei (Standard: 1) |
-t SEP | Feldtrenner — gilt für Eingabe und Ausgabe |
-i | Vergleich case-insensitive |
-a 1 | Auch nicht-matchende Zeilen aus File 1 ausgeben (LEFT JOIN) |
-a 2 | Auch nicht-matchende Zeilen aus File 2 ausgeben (RIGHT JOIN) |
-v 1 | Nur nicht-matchende Zeilen aus File 1 (Anti-Join) |
-v 2 | Nur nicht-matchende Zeilen aus File 2 (Anti-Join) |
-e STRING | Ersatzwert für leere Felder bei -a |
-o FORMAT | Ausgabe-Spalten explizit wählen, z. B. 1.1,2.2,1.3 |
--header | Erste Zeile beider Dateien als Header behandeln (GNU-Erweiterung) |
Inner Join — der Default
Ohne Zusatzoptionen produziert join einen Inner Join: Zeilen kommen nur in die Ausgabe, wenn der Schlüssel in beiden Dateien existiert. Datensätze ohne Match fallen heraus.
cat users.txt
cat orders.txt
join users.txt orders.txt1 Anna
2 Bernd
3 Clara
4 David
1 250
2 95
3 410
1 Anna 250
2 Bernd 95
3 Clara 410David (ID 4) hat keine Bestellung und taucht im Ergebnis nicht auf.
Outer und Anti-Join
Mit -a werden auch unmatched Zeilen mitgenommen — der Schalter steht hinter der Dateinummer. -a 1 entspricht einem LEFT JOIN, -a 2 einem RIGHT JOIN, beide zusammen einem FULL OUTER JOIN.
join -a 1 -e 'NULL' -o '1.1,1.2,2.2' users.txt orders.txt1 Anna 250
2 Bernd 95
3 Clara 410
4 David NULL-v dreht die Logik um: Es liefert ausschließlich die nicht-matchenden Zeilen — perfekt, um Lücken zu finden:
join -v 1 users.txt orders.txt4 DavidCSV-Beispiel
Ein realistischeres Setup mit zwei CSV-Dateien, die per user_id verknüpft werden sollen. Beide sind nach dem ersten Feld sortiert.
cat users.csv
echo '---'
cat orders.csv1,Anna,Berlin
2,Bernd,Hamburg
3,Clara,München
4,David,Köln
---
1,250,Buch
2,95,DVD
3,410,Laptop
5,30,HeftInner Join über user_id, beide Dateien CSV-getrennt:
join -t, -1 1 -2 1 users.csv orders.csv1,Anna,Berlin,250,Buch
2,Bernd,Hamburg,95,DVD
3,Clara,München,410,LaptopFull Outer Join mit Platzhaltern und expliziter Spaltenwahl:
join -t, -a 1 -a 2 -e '-' -o '0,1.2,2.2,2.3' users.csv orders.csv1,Anna,250,Buch
2,Bernd,95,DVD
3,Clara,410,Laptop
4,David,-,-
5,-,30,Heft0 steht im -o-Format für das Join-Feld selbst, 1.2 für Feld 2 aus File 1, 2.3 für Feld 3 aus File 2.
Praxis
Logfiles korrelieren — User-IDs in einem Access-Log mit Klarnamen aus einer User-Liste anreichern.
join -1 2 -2 1 <(sort -k2 access.log) <(sort users.txt)ID-zu-Name-Mapping — eine schlanke Übersetzungstabelle anwenden, ohne awk oder Skripte zu bemühen.
Fehlende Einträge finden — mit -v 1 listet man genau die User auf, die in der zweiten Datei keinen Eintrag haben. Das entspricht in SQL einem LEFT JOIN ... WHERE rechte_seite IS NULL.
join -t, -v 1 users.csv orders.csvHäufige Stolperfallen
Unsortierte Dateien sind die häufigste Fehlerquelle
join setzt zwingend voraus, dass beide Eingabedateien nach dem Join-Feld sortiert sind — und zwar in derselben Sortierordnung wie die, die join intern erwartet. Bei deutschen Locales sortiert sort anders als join standardmäßig vergleicht. Wenn das Ergebnis unerwartet leer oder unvollständig ist, ist fast immer die Sortierung schuld. Sicherheitshalber überall LC_ALL=C voranstellen — das macht die Reihenfolge byte-deterministisch und POSIX-konform.
Standard-Trenner ist Whitespace — mehrere Spaces werden zusammengefasst
Ohne -t betrachtet join jede Folge von Leerzeichen oder Tabs als einen Trenner. Das bedeutet: leere Felder gibt es in dieser Konfiguration nicht. Wer mit Tabulatoren arbeiten will, muss -t $'\t' explizit setzen. Bei CSV ist -t, Pflicht — sonst zerlegt join die Zeile am ersten Whitespace innerhalb eines Felds.
Bei `-t` muss in beiden Files derselbe Trenner stehen
join kann nur einen Trenner pro Aufruf verarbeiten. Wenn File 1 mit Komma getrennt ist und File 2 mit Semikolon, müssen beide vorher harmonisiert werden — etwa mit tr oder sed. Sonst landet der ganze Rest jeder Zeile in einem einzigen Feld.
Header-Zeilen werden NICHT übersprungen
Hat eine CSV-Datei Spaltenüberschriften, behandelt join diese wie normale Daten — und bricht den Vergleich, sobald die Header-Zeile lexikalisch nicht in die Sortierreihenfolge passt. Entweder Header per tail -n +2 entfernen oder die GNU-Erweiterung --header verwenden, die in vielen Coreutils-Versionen verfügbar ist.
Leere Zeilen brechen den Join
Eine leere Zeile mitten in einer Datei sortiert ans Anfang oder Ende und kann den Vergleich aus dem Tritt bringen. Vor join immer leere Zeilen filtern, etwa mit grep -v '^$' oder sed '/^$/d'.
`-o` mit komplexem Format ist mächtig, aber tückisch
Das -o-Format akzeptiert eine kommagetrennte Liste aus FILE.FELD-Tokens, plus die Sonderform 0 für das Join-Feld. Tippfehler wie 1.0 oder 0.1 sind nicht intuitiv erkennbar — join schluckt sie und produziert leise unsinnige Ausgaben. Wer das Format nutzt, sollte das Ergebnis mit einer kleinen Testdatei verifizieren.
Weiterführende Ressourcen
Externe Quellen
- join(1) – Linux manual page (man7.org) — offizielle Dokumentation aller join-Optionen
- GNU Coreutils Manual: join invocation — ausführliche Beschreibung im GNU-Handbuch
- Unix Stack Exchange: join examples — Praxisbeispiele und Edge Cases der Community
- POSIX join specification — formale POSIX-Definition
Verwandte Artikel
- paste – Zeilen spaltenweise zusammenfügen — der einfache Spalten-Merge ohne Schlüssel
- sort – Zeilen sortieren — Pflichtschritt vor jedem join
- cut – Spalten extrahieren — einzelne Felder vor dem Join herauslösen
- awk – Textverarbeitung mit Skripten — flexibler bei komplexen Joins ohne Sortierzwang
- grep – Muster in Texten finden — zum Vor- oder Nachfiltern der Eingaben