Devin.KR
로그인

리눅스 join 명령어 사용법 - 두 파일을 키로 합치는 셸 위의 SQL JOIN

개발자 조회 1

이 명령어를 언제 쓰는가

공통 키를 가진 두 텍스트 파일을 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 JOINjoin -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 — 키 매칭 없이 줄 순서대로 옆에 붙이는 단순한 형태다.