grep | sort | uniq는 텍스트에서 원하는 줄만 추출하고, 같은 값을 한곳에 모은 뒤, 중복을 제거하거나 개수를 세기 위한 조합이다.
이 조합이 필요한 이유는 uniq가 전체 중복을 찾는 도구가 아니라 인접한 동일 라인만 처리하기 때문이다.
핵심 차이는 grep이 필터링, sort가 그룹화 준비, uniq가 집계를 맡는다는 점이다.

핵심 요약

grep만 쓰면 조건에 맞는 줄만 볼 수 있다.
uniq만 쓰면 붙어 있는 중복만 처리된다.
sort를 중간에 넣어야 동일한 값이 모이고, 그 다음에 uniq가 의미 있는 결과를 만든다.

왜 필요한가

로그 파일이나 텍스트 데이터는 보통 정리된 상태로 존재하지 않는다. 같은 에러 메시지가 여러 번 나타나더라도 파일 안에서는 시간순으로 섞여 있다. 중간에 다른 로그가 끼어 있고, 같은 메시지가 떨어진 위치에 다시 등장한다. 이런 상태에서는 눈으로 읽을 수는 있어도, 어떤 패턴이 반복되는지 바로 파악하기 어렵다.

기존 방식은 두 가지 한계를 가진다. 첫째, grep만 사용하면 특정 문자열이 포함된 줄은 볼 수 있지만, 어떤 메시지가 몇 번 반복되었는지는 정리되지 않는다. 둘째, uniq만 사용하면 중복을 제거할 수 있을 것처럼 보이지만 실제로는 붙어 있는 동일 줄만 처리하므로, 시간순 로그처럼 흩어진 데이터에서는 결과가 왜곡된다. 즉, 필터링과 집계 사이에 정렬이라는 전처리 단계가 없으면 중복 분석 자체가 성립하지 않는다.

이때 해결 구조가 grep | sort | uniq다. 먼저 grep이 필요한 데이터만 남긴다. 그다음 sort가 같은 문자열을 한곳에 모은다. 마지막으로 uniq가 인접한 동일 값을 제거하거나 개수를 센다. 이 구조는 단순한 명령어 나열이 아니라, 비정형 텍스트를 패턴 단위로 재구성하는 처리 순서다. 실제 영향도 명확하다. 로그에서 가장 많이 발생한 에러를 찾거나, 요청 경로 중 중복이 많은 항목을 확인하거나, 사용자 목록에서 중복 데이터를 정리할 때 같은 패턴을 그대로 적용할 수 있다.

예제

예제 1. 특정 에러 줄만 추출한 뒤 개수 세기

grep ERROR app.log | sort | uniq -c

예상 결과:

12 ERROR Connection failed
4 ERROR Timeout
2 ERROR Disk full

grep ERRORERROR가 포함된 줄만 남긴다.
sort는 같은 에러 문장을 서로 붙여 놓는다.
uniq -c는 붙어 있는 동일 줄의 개수를 센다.
이 조합은 “어떤 에러가 몇 번 나왔는가”를 보고 싶을 때 쓴다. 단순 조회가 아니라 집계가 목적일 때 필요한 구조다.

예제 2. sort 없이 uniq -c를 바로 사용한 경우

grep ERROR app.log | uniq -c

예상 결과:

1 ERROR Connection failed
1 ERROR Timeout
1 ERROR Connection failed

같은 에러가 여러 번 있어도, 중간에 다른 줄이 끼어 있으면 अलग-अलग 항목처럼 나온다.
이유는 uniq가 전체 데이터를 스캔해 중복을 찾는 것이 아니라, 현재 줄과 바로 이전 줄만 비교하기 때문이다.
이 예제는 uniq를 중복 제거 도구라고만 이해하면 왜 오해가 생기는지 보여준다.
실무에서는 이 결과를 보고 “로그가 중복되지 않았다”라고 해석하면 잘못된 판단이 된다.

예제 3. 가장 많이 발생한 항목을 위로 올리기

grep ERROR app.log | sort | uniq -c | sort -nr

예상 결과:

120 ERROR Connection failed
45 ERROR Timeout
9 ERROR Disk full

앞 단계의 uniq -c는 개수와 문자열을 함께 출력한다.
마지막 sort -nr은 맨 앞 숫자를 기준으로 내림차순 정렬한다.
이렇게 하면 가장 많이 발생한 항목이 위로 올라온다.
언제 쓰는가. 장애 분석이나 우선순위 정리가 필요할 때 쓴다. 개수 자체보다 어떤 문제가 가장 큰 비중을 차지하는지가 중요할 때 유용하다.

예제 4. 중복 사용자 목록 정리

cat users.txt | sort | uniq

예상 결과:

alice
bob
charlie

사용자 목록 파일에 같은 이름이 여러 번 들어 있을 수 있다.
sort가 먼저 이름을 정렬해서 같은 값을 붙인다.
uniq는 붙어 있는 중복 이름을 하나로 줄인다.
이 구조는 로그 분석뿐 아니라, 단순한 텍스트 정리에도 그대로 적용된다. CSV에서 특정 열만 뽑아 처리할 때도 같은 원리로 확장할 수 있다.

예제 5. 요청 경로별 빈도 확인

awk '{print $7}' access.log | sort | uniq -c | sort -nr | head

예상 결과:

340 /api/login
210 /api/order
90 /health

여기서는 grep 대신 awk로 특정 필드만 추출했다.
핵심 구조는 그대로다. 추출 → 정렬 → 집계 → 빈도순 재정렬이다.
즉, grep | sort | uniq는 하나의 고정 공식이 아니라, 필터링 단계만 바꿔서 재사용할 수 있는 처리 패턴이다.
언제 쓰는가. 어떤 API가 가장 자주 호출되는지, 특정 값의 분포가 어떤지 빠르게 확인할 때 쓴다.

실무 적용

1. 장애 로그에서 반복 에러 확인

상황은 장애 발생 직후다. 로그는 수만 줄이고, 에러 종류도 여러 개가 섞여 있다.
문제는 단순 검색만으로는 어떤 에러가 핵심 원인인지 보이지 않는다는 점이다.
적용 방법은 grep ERROR app.log | sort | uniq -c | sort -nr처럼 필터링 후 집계하는 것이다.
효과는 가장 자주 발생한 에러를 바로 위로 올려서, 조사 순서를 정할 수 있다는 점이다. 원인 후보를 줄이는 데 시간이 덜 든다.

2. 배치 결과 파일에서 중복 데이터 확인

상황은 배치 작업 후 결과 파일 검증이다.
문제는 동일한 ID가 중복 저장되었는지 육안으로 찾기 어렵다는 점이다.
적용 방법은 ID 컬럼만 추출한 뒤 sort | uniq -c를 사용하는 것이다.
효과는 중복 건수를 즉시 확인할 수 있다는 점이다. 데이터 정합성 검증에서 누락보다 중복이 먼저 드러난다.

3. 접근 로그에서 집중 트래픽 경로 찾기

상황은 특정 시간대에 서비스 지연이 발생한 경우다.
문제는 어떤 URI가 부하를 만들었는지 바로 보이지 않는다는 점이다.
적용 방법은 요청 경로만 뽑아서 정렬 후 집계하는 것이다.
효과는 호출 집중 지점을 빠르게 확인할 수 있다는 점이다. 캐시 대상, 제한 대상, 튜닝 대상을 분리하기 쉬워진다.

4. 텍스트 목록 정리 자동화

상황은 여러 파일에서 추출한 값 목록을 정리하는 경우다.
문제는 중복 제거와 정렬을 수작업으로 하면 반복 비용이 커진다는 점이다.
적용 방법은 셸 스크립트 안에서 sort | uniq를 표준 단계로 넣는 것이다.
효과는 결과 형식이 항상 같아진다는 점이다. 후속 스크립트가 입력 형식을 예측할 수 있어서 자동화가 쉬워진다.

흔한 실수

실수 1. uniq가 전체 중복을 찾는다고 생각하는 경우

잘못된 사용:

cat app.log | uniq -c

실제 결과는 떨어져 있는 중복이 따로 계산된다.
왜 틀렸는지 설명하면, uniq는 해시 기반 전체 비교를 하지 않고 인접 비교만 수행하기 때문이다.
올바른 방법은 먼저 정렬하는 것이다.

cat app.log | sort | uniq -c

실수 2. grep 뒤에 바로 uniq를 붙이는 경우

잘못된 사용:

grep ERROR app.log | uniq

실제 결과는 연속된 같은 에러만 하나로 줄어든다.
중간에 다른 에러가 끼면 다시 별도 항목으로 남는다.
원인은 grep이 필터링만 할 뿐, 같은 값을 모으지 않기 때문이다.
해결은 sort를 중간에 넣는 것이다.

grep ERROR app.log | sort | uniq

실수 3. 개수 세기 후 숫자 정렬을 하지 않는 경우

잘못된 사용:

grep ERROR app.log | sort | uniq -c | sort

실제 결과는 숫자 크기 기준이 아니라 문자열 기준으로 정렬될 수 있다.
예를 들어 120보다 9가 뒤에 오지 않을 수 있다.
원인은 기본 sort가 숫자 의미를 이해하지 않고 텍스트로 비교하기 때문이다.
해결은 숫자 내림차순 옵션을 주는 것이다.

grep ERROR app.log | sort | uniq -c | sort -nr

실수 4. 너무 이른 단계에서 원본 정보를 잃는 경우

잘못된 사용은 필요한 필드 추출 없이 전체 줄을 바로 집계하는 것이다.

cat access.log | sort | uniq -c

실제 결과는 타임스탬프나 IP가 달라서 거의 모든 줄이 다른 값처럼 보인다.
왜 틀렸는지 설명하면, 로그 한 줄 전체가 비교 대상이 되기 때문이다.
요청 경로나 에러 코드처럼 분석 단위가 되는 필드를 먼저 추출해야 한다.
올바른 방법은 awk, cut, grep 등을 이용해 비교 대상을 좁히는 것이다.

awk '{print $7}' access.log | sort | uniq -c | sort -nr

실수 5. sort가 단지 보기 좋게 정렬하는 용도라고 생각하는 경우

잘못된 인식은 sort를 생략 가능한 장식 단계로 보는 것이다.
실제 결과는 uniq의 집계가 깨진다.
원인은 sort가 미관용 정렬이 아니라, 같은 값을 인접하게 배치하는 전처리 단계이기 때문이다.
해결은 uniq 앞의 sort를 출력 꾸미기가 아니라 처리 논리의 일부로 이해하는 것이다.

관련 개념

cutawk는 비교 대상이 되는 필드만 추출할 때 함께 사용된다.
sort -u는 정렬과 중복 제거를 한 번에 처리하지만, 개수는 세지 못한다.
wc -l은 최종 결과 줄 수를 세는 데 쓰이며, 집계 결과 검증과 함께 자주 붙는다.
headtail은 집계 후 상위 항목이나 하위 항목만 볼 때 연결된다.

더 깊이 보기

uniq가 인접 비교만 하는 이유는 동작 방식이 단순하기 때문이다. 입력 스트림을 처음부터 끝까지 읽으면서 이전 줄 하나만 기억하면 된다. 이 구조는 메모리 사용이 작고 구현도 단순하다. 대신 전체 중복을 찾는 기능은 제공하지 않는다. 그래서 그 역할을 sort가 앞단에서 대신한다.

sort의 구조적 역할은 정렬 그 자체가 아니다. 핵심은 동일 값을 서로 붙여 놓는 것이다. 이 과정을 통해 원래 시간순이던 데이터가 “값 기준 그룹 순서”로 재배열된다. 즉, sort는 출력 미관을 바꾸는 것이 아니라 데이터의 비교 가능성을 바꾼다. 그 다음 단계인 uniq는 바뀐 데이터 구조 위에서만 제대로 동작한다.

시스템 관점에서 보면 이 조합은 유닉스 철학을 그대로 따른다. 각 도구는 하나의 역할만 담당한다. grep은 선택, sort는 재배열, uniq는 집계다. 하나의 거대한 명령이 아니라 작은 도구를 연결해 더 큰 의미를 만든다. 이 방식이 유지보수에 유리한 이유도 여기 있다. 필터링 단계만 다른 도구로 교체해도 전체 패턴은 그대로 유지된다.

정리

grep | sort | uniq는 텍스트를 필터링하고, 같은 값을 모으고, 중복을 제거하거나 개수를 세는 처리 패턴이다.
여기서 sort는 선택 단계가 아니라 uniq가 동작하기 위한 전처리 단계다.
로그 분석, 중복 데이터 검증, 요청 분포 확인 같은 작업은 이 구조로 빠르게 해결할 수 있다.
핵심은 명령어를 외우는 것이 아니라, 추출 → 그룹화 → 집계라는 흐름을 이해하는 것이다.