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.

Bash Grundprinzip
join users.txt orders.txt
Output
1 Anna 250
2 Bernd 95
3 Clara 410

Konzeptuell 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:

Bash Sicher sortieren
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.txt

Optionen

OptionBedeutung
-1 NFeldnummer in der ersten Datei (Standard: 1)
-2 NFeldnummer in der zweiten Datei (Standard: 1)
-t SEPFeldtrenner — gilt für Eingabe und Ausgabe
-iVergleich case-insensitive
-a 1Auch nicht-matchende Zeilen aus File 1 ausgeben (LEFT JOIN)
-a 2Auch nicht-matchende Zeilen aus File 2 ausgeben (RIGHT JOIN)
-v 1Nur nicht-matchende Zeilen aus File 1 (Anti-Join)
-v 2Nur nicht-matchende Zeilen aus File 2 (Anti-Join)
-e STRINGErsatzwert für leere Felder bei -a
-o FORMATAusgabe-Spalten explizit wählen, z. B. 1.1,2.2,1.3
--headerErste 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.

Bash Standardverhalten
cat users.txt
cat orders.txt
join users.txt orders.txt
Output
1 Anna
2 Bernd
3 Clara
4 David
1 250
2 95
3 410
1 Anna 250
2 Bernd 95
3 Clara 410

David (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.

Bash LEFT JOIN mit Ersatzwert
join -a 1 -e 'NULL' -o '1.1,1.2,2.2' users.txt orders.txt
Output
1 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:

Bash Anti-Join: User ohne Order
join -v 1 users.txt orders.txt
Output
4 David

CSV-Beispiel

Ein realistischeres Setup mit zwei CSV-Dateien, die per user_id verknüpft werden sollen. Beide sind nach dem ersten Feld sortiert.

Bash Eingabedateien
cat users.csv
echo '---'
cat orders.csv
Output
1,Anna,Berlin
2,Bernd,Hamburg
3,Clara,München
4,David,Köln
---
1,250,Buch
2,95,DVD
3,410,Laptop
5,30,Heft

Inner Join über user_id, beide Dateien CSV-getrennt:

Bash Inner Join CSV
join -t, -1 1 -2 1 users.csv orders.csv
Output
1,Anna,Berlin,250,Buch
2,Bernd,Hamburg,95,DVD
3,Clara,München,410,Laptop

Full Outer Join mit Platzhaltern und expliziter Spaltenwahl:

Bash Full Outer mit -o
join -t, -a 1 -a 2 -e '-' -o '0,1.2,2.2,2.3' users.csv orders.csv
Output
1,Anna,250,Buch
2,Bernd,95,DVD
3,Clara,410,Laptop
4,David,-,-
5,-,30,Heft

0 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.

Bash Logs 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.

Bash User ohne Bestellung
join -t, -v 1 users.csv orders.csv

Hä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

/ Weiter

Zurück zu Textverarbeitung

Zur Übersicht