objectif
Assembler deux listes/fichiers par une clé commune (comme un JOIN SQL) pour croiser des données: utilisateurs vs rôles, routes exposées vs autorisées, data.pm vs api.data.pm. Gestion des séparateurs, clés sur autres colonnes, outer join et formatage.
code minimal
# inner join simple sur 1re colonne (fichiers triés par clé)
LC_ALL=C sort -u A.txt > A.sorted
LC_ALL=C sort -u B.txt > B.sorted
join A.sorted B.sorted
utilisation
# 1) CSV (séparateur virgule), clé colonne 1 dans les deux fichiers
# users.csv: user_id,name
# roles.csv: user_id,role
join -t ',' -1 1 -2 1 \
<(LC_ALL=C sort -t',' -k1,1 users.csv) \
<(LC_ALL=C sort -t',' -k1,1 roles.csv)
# sortie: user_id,name,role (join colle les champs; cf. -o pour choisir)
# 2) formater la sortie (choisir colonnes, ajouter séparateur)
# -o 0=clé join, 1.2=champ 2 du fichier1, 2.2=champ 2 du fichier2
join -t ',' -o 0,1.2,2.2 \
<(LC_ALL=C sort -t',' -k1,1 users.csv) \
<(LC_ALL=C sort -t',' -k1,1 roles.csv)
# 3) outer join (left/right/full) avec -a et valeurs manquantes -e
# left outer: tout users.csv, même si pas de role
join -t ',' -a 1 -e 'NA' -o 0,1.2,2.2 \
<(LC_ALL=C sort -t',' -k1,1 users.csv) \
<(LC_ALL=C sort -t',' -k1,1 roles.csv)
# right outer
join -t ',' -a 2 -e 'NA' -o 0,1.2,2.2 \
<(LC_ALL=C sort -t',' -k1,1 users.csv) \
<(LC_ALL=C sort -t',' -k1,1 roles.csv)
# full outer (union des clés): -a 1 -a 2
join -t ',' -a 1 -a 2 -e 'NA' -o 0,1.2,2.2 \
<(LC_ALL=C sort -t',' -k1,1 users.csv) \
<(LC_ALL=C sort -t',' -k1,1 roles.csv)
# 4) clé sur d'autres colonnes (ex: routes: path en col 2 vs 1)
# exposed.csv: id,/v1/users,GET
# allowed.csv: /v1/users,200
join -t ',' -1 2 -2 1 -o 1.2,1.3,2.2 \
<(LC_ALL=C sort -t',' -k2,2 exposed.csv) \
<(LC_ALL=C sort -t',' -k1,1 allowed.csv)
# 5) ignorer la casse avec -i (attention aux locales)
join -i -t ',' -1 1 -2 1 \
<(LC_ALL=C sort -f -t',' -k1,1 routes_data.pm.csv) \
<(LC_ALL=C sort -f -t',' -k1,1 routes_api.data.pm.csv)
# 6) fichiers avec en-têtes: strip header, join, puis réinsérer le header
{
printf "user_id,name,role\n"
join -t ',' -o 0,1.2,2.2 \
<(LC_ALL=C (read -r; sort -t',' -k1,1) < users.csv) \
<(LC_ALL=C (read -r; sort -t',' -k1,1) < roles.csv)
} > users_roles.join.csv
variante(s) utile(s)
# A) afficher uniquement les clés non appariées (anti-join)
# non appariées dans A (pas dans B)
join -t ',' -v 1 \
<(LC_ALL=C sort -t',' -k1,1 A.csv) \
<(LC_ALL=C sort -t',' -k1,1 B.csv)
# non appariées dans B
join -t ',' -v 2 \
<(LC_ALL=C sort -t',' -k1,1 A.csv) \
<(LC_ALL=C sort -t',' -k1,1 B.csv)
# B) join sur TSV (tabulation) - plus sûr si les valeurs contiennent des virgules
join -t $'\t' -1 1 -2 1 \
<(LC_ALL=C sort -t $'\t' -k1,1 a.tsv) \
<(LC_ALL=C sort -t $'\t' -k1,1 b.tsv)
# C) plusieurs clés (concaténer une clé composite proprement)
# users: org,user_id,name
# roles: org,user_id,role
join -t ',' -1 1 -2 1 -o 1.1,1.2,1.3,2.3 \
<(LC_ALL=C awk -F',' 'BEGIN{OFS=","}{print $1"-"$2,$0}' users.csv | sort -t',' -k1,1) \
<(LC_ALL=C awk -F',' 'BEGIN{OFS=","}{print $1"-"$2,$0}' roles.csv | sort -t',' -k1,1)
# D) données non triées et/ou clés dupliquées: awk comme fallback (inner join)
# (charge B en mémoire; O(N+M); gère doublons)
awk -F',' 'NR==FNR{b[$1]=$0; next} ($1 in b){print $0","b[$1]}' roles.csv users.csv
# E) CSV RFC (quotes, virgules échappées): utilisez csvkit (si dispo)
# pip install csvkit
csvjoin -c user_id users.csv roles.csv | csvcut -c user_id,name,role > users_roles.csv
# F) audit de routes data.pm vs allowlist api.data.pm (diff direct)
join -t ',' -v 1 \
<(LC_ALL=C cut -d, -f1 routes_exposees.csv | sort -u | awk 'NF') \
<(LC_ALL=C cut -d, -f1 routes_autorisees.csv | sort -u | awk 'NF') \
| sed '1s/^/exposees_non_autorisees\n/'
notes
joinexige que les deux fichiers soient triés selon la colonne clé (même ordre et même locale). UtilisezLC_ALL=C sort -t',' -kX,X.- Définissez le séparateur avec
-t(,pour CSV,$'\t'pour TSV). Sans-t,jointraite des champs séparés par espaces. -ocontrôle les colonnes de sortie (0=clé;1.N=champ N du fichier 1;2.Ndu fichier 2).-edéfinit la valeur pour les champs manquants.-a 1/-a 2incluent les lignes orphelines (left/right outer).-v 1/-v 2affichent uniquement les non appariées (anti-join).- Pour des CSV complexes (quotes, virgules dans les champs), préférez des outils dédiés (
csvjoin,mlr) ou un script (Python). Pour des listes simples,joinest léger, rapide et fiable.