리눅스 join 명령어 사용법 - 두 파일을 키로 합치는 셸 위의 SQL JOIN
이 명령어를 언제 쓰는가
공통 키를 가진 두 텍스트 파일을 SQL 의 JOIN 처럼 합칠 때 쓴다. 서버 목록과 서버별 사용량 집계, 사용자 ID 목록과 접속 통계처럼 "한쪽에는 이름, 다른 쪽에는 숫자"가 있는 상황이 실무에서 계속 생긴다.
DB에 넣고 SQL 을 짜기엔 과한 일회성 대조 작업에서 join 한 줄이면 끝난다. 다만 join 은 두 입력이 키 기준으로 정렬되어 있다고 무조건 믿는다. 이 전제를 어기면 에러 없이 결과 일부가 사라진다. join 을 쓸 줄 안다는 건 이 함정을 안다는 뜻이다.
기본 형식
join [-t 구분자] [-1 N] [-2 M] 파일1 파일2
- 기본적으로 양쪽의 1번 필드를 키로 보고, 키가 같은 줄끼리 이어 붙인다.
-1 N/-2 M으로 각 파일의 키 필드 위치를 따로 지정한다.-t로 구분자를 지정한다. 지정하지 않으면 공백(연속 공백 포함)이 구분자다.- 출력은
키 파일1의나머지 파일2의나머지순서다.
자주 쓰는 옵션
| 옵션 | 의미 | 예시 |
|---|---|---|
-t C | 필드 구분자 지정. CSV 라면 필수다 | join -t, a.csv b.csv |
-1 N, -2 M | 파일1·파일2의 키 필드 번호 | join -1 2 -2 1 a b |
-a N | 짝이 없는 N번 파일의 줄도 출력한다. SQL 의 LEFT/RIGHT JOIN | join -a 1 a b |
-v N | 짝이 없는 줄만 출력한다. 차집합 확인용 | join -v 1 a b |
-o LIST | 출력할 필드를 직접 지정한다. 0은 키, 1.2는 파일1의 2번 필드 | join -o '0,1.2,2.2' a b |
-e STR | 빈 자리를 채울 문자열. -a·-o와 함께 쓴다 | join -a 1 -e NULL -o '0,2.2' a b |
-i | 키 비교 시 대소문자를 무시한다 (GNU 전용, BSD 에는 없다) | join -i a b |
실전 예제
아래 예제는 이런 두 파일을 가정한다.
$ cat items.csv
1,apple
2,banana
3,cherry
$ cat stock.csv
2,10
3,20
4,30
1) 양쪽에 다 있는 키만 합친다. 기본 동작이고 SQL 의 INNER JOIN 에 해당한다.
join -t, items.csv stock.csv
2,banana,10
3,cherry,20
2) 왼쪽 파일은 전부 남긴다. 재고 정보가 없는 품목도 목록에 보여야 하는 상황이다.
join -t, -a 1 items.csv stock.csv
1,apple
2,banana,10
3,cherry,20
짝이 없는 줄은 필드 수가 줄어 그대로 나온다. 열 개수를 맞추려면 -o와 -e를 같이 쓴다.
join -t, -a 1 -e NULL -o '0,1.2,2.2' items.csv stock.csv
1,apple,NULL
2,banana,10
3,cherry,20
3) 한쪽에만 있는 항목을 찾는다. 배포 대상 목록과 실제 배포된 목록을 대조해 누락을 잡을 때 쓴다.
join -t, -v 1 items.csv stock.csv
1,apple
4) 키 위치가 서로 다른 파일을 합친다. 한쪽은 첫 열이 키, 다른 쪽은 두 번째 열이 키인 흔한 상황이다.
join -t, -1 1 -2 2 <(sort -t, -k1,1 users.csv) <(sort -t, -k2,2 logins.csv)
5) 정렬부터 확실히 하고 넘긴다. 실무에서는 이 형태로 쓰는 것이 안전하다.
join -t, <(LC_ALL=C sort -t, -k1,1 items.csv) <(LC_ALL=C sort -t, -k1,1 stock.csv)
함정과 주의점
정렬되지 않은 입력에서는 결과가 조용히 잘린다. 이게 join 사고의 90%다. GNU join 은 join: file 1 is not in sorted order 경고라도 내지만, macOS·FreeBSD 의 BSD join 은 아무 말 없이 매칭 일부를 빠뜨린 결과를 낸다. 스크립트에서는 티가 안 나고, 나중에 데이터가 비어 있는 것으로 발견된다.
$ cat unsorted.csv
3,c
1,a
$ join -t, unsorted.csv stock.csv
3,c,20
1번 키는 stock.csv 에 짝이 없어서가 아니라 정렬이 안 돼서 검사조차 되지 않았다. 항상 sort 를 먼저 태운다.
sort 와 join 의 정렬 기준이 다르면 정렬을 해도 틀어진다. ko_KR.UTF-8 같은 로케일에서 sort 는 하이픈·언더스코어를 무시하고 비교하지만, join 의 키 비교는 그와 다르게 동작할 수 있다. 결과적으로 "분명히 sort 했는데 매칭이 빠진다". 해법은 양쪽 모두 LC_ALL=C 로 통일하는 것이다.
LC_ALL=C sort -t, -k1,1 a.csv > a.sorted
LC_ALL=C sort -t, -k1,1 b.csv > b.sorted
LC_ALL=C join -t, a.sorted b.sorted
sort -k1,1 처럼 필드를 끊어서 지정한다. 그냥 sort 를 쓰면 줄 전체를 기준으로 정렬해서 키가 같아도 순서가 join 의 기대와 어긋날 수 있다.
CSV 안에 따옴표로 감싼 콤마가 있으면 join 은 못 쓴다. join 은 CSV 파서가 아니라 단순 구분자 분리기다. 따옴표 처리가 필요한 데이터라면 python 이나 csvkit 계열 도구로 가야 한다.
함께 보면 좋은 명령어
sort— join 의 필수 전처리. 같은 로케일·같은 키로 정렬해야 한다.comm— 키가 아니라 줄 전체를 비교해 교집합·차집합만 보고 싶을 때 더 간단하다.paste— 키 매칭 없이 줄 순서대로 옆에 붙이는 단순한 형태다.