리눅스 awk 명령어 사용법 - 로그 분석과 집계 실전 예제 정리
이 명령어를 언제 쓰는가
트래픽이 갑자기 튀었을 때 어느 IP 가 때리는지 세어야 하고, 상태코드별 비율을 봐야 하고, 응답이 3초 넘는 요청만 뽑아야 한다. 이런 일은 grep 으로 찾고 cut 으로 자르는 조합으로는 금방 한계가 온다. awk는 한 줄을 열로 쪼개서, 조건을 걸고, 값을 누적하는 일을 한 번에 한다.
정식 프로그래밍 언어지만 실무에서 쓰는 문법은 열 줄 남짓이다. {print $1}, -F, 조건식, 그리고 배열 누적과 END 블록. 이 네 가지만 익히면 로그 분석 작업의 대부분이 파이프 한 줄로 끝난다. 별도 도구를 설치할 수 없는 운영 서버에서 특히 값어치가 크다.
기본 형식
awk [옵션] '패턴 { 동작 }' 파일
- 패턴 — 생략하면 모든 줄.
/에러/,$9 == 500,NR > 1처럼 조건을 쓴다. - 동작 — 생략하면 그 줄을 그대로 출력한다. 즉
awk '/error/'는 grep 과 같다. - $1, $2 … — 공백으로 나뉜 첫째, 둘째 열.
$0은 줄 전체,$NF는 마지막 열이다. - NR 은 지금까지 읽은 줄 번호, NF 는 이 줄의 열 개수다.
- BEGIN / END 블록은 각각 처음과 끝에 한 번씩 실행된다. 집계 결과는 END 에서 출력한다.
자주 쓰는 옵션과 변수
| 옵션·변수 | 의미 | 예시 |
|---|---|---|
-F 구분자 | 열 구분자 지정(기본은 공백) | awk -F: '{print $1}' /etc/passwd |
-v 이름=값 | 셸 값을 awk 변수로 넘긴다 | awk -v lim=500 '$10 > lim' |
$0 / $NF | 줄 전체 / 마지막 열 | awk '{print $NF}' a.log |
NR | 현재 줄 번호(전체 기준) | awk 'NR > 1' data.csv |
NF | 이 줄의 열 개수 | awk 'NF != 12' a.log |
OFS | 출력할 때 쓸 구분자 | awk -v OFS=, '{print $1,$2}' |
BEGIN{} / END{} | 시작 전 / 전부 읽은 뒤 한 번 실행 | awk '{s+=$1} END{print s}' |
배열[키]++ | 키별 개수를 센다 | awk '{c[$1]++} END{for(k in c) print c[k],k}' |
실전 예제
1. 트래픽을 많이 발생시키는 IP 를 찾는다. 서버 부하가 올라갔을 때 가장 먼저 돌리는 한 줄이다.
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
48213 203.0.113.44
1921 198.51.100.7
884 192.0.2.13
1위가 나머지를 압도한다면 크롤러이거나 공격이다. 정상 사용자 분포는 이렇게 나오지 않는다.
2. 상태코드별로 집계한다. 5xx 가 몇 건인지, 404 가 늘었는지 한눈에 본다. nginx 기본 combined 포맷에서 상태코드는 9번째 열이다.
awk '{c[$9]++} END {for (k in c) printf "%s\t%d\n", k, c[k]}' /var/log/nginx/access.log | sort -k2 -rn
200 51204
304 3311
404 812
500 17
3. 특정 시간대의 5xx 요청만 URL 과 함께 뽑는다. 장애 시각이 정해졌을 때 원인 URL 을 좁히는 작업이다.
awk '$9 ~ /^5/ && $4 ~ /25\/Aug\/2026:03:/ {print $9, $7}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head
4. 디스크 사용률이 임계치를 넘은 파티션만 알린다. 감시 스크립트에 그대로 넣는 형태다. $5 는 87% 처럼 퍼센트 기호가 붙어 있으므로 +0 으로 숫자로 강제 변환한다.
df -P | awk -v lim=90 'NR > 1 && $5 + 0 >= lim {print $6, $5}'
/var 93%
df 대신 df -P 를 쓴 이유는, 장치명이 길면 기본 출력이 두 줄로 접혀 열 번호가 어긋나기 때문이다.
5. 응답 시간 평균과 최댓값을 낸다. 로그 포맷에 $request_time 을 마지막 열로 넣어두었을 때의 예다.
awk '{s += $NF; n++; if ($NF > max) max = $NF} END {printf "avg=%.3f max=%.3f n=%d\n", s/n, max, n}' /var/log/nginx/access.log
6. 콜론으로 나뉜 시스템 파일을 읽는다. 로그인 가능한 계정만 추릴 때 쓴다.
awk -F: '$7 !~ /(nologin|false)$/ {print $1, $3, $7}' /etc/passwd
7. 프로세스가 실제로 쓰는 메모리를 합산한다. 특정 애플리케이션이 전체로 얼마를 먹는지 볼 때다.
ps -eo rss,comm --no-headers | awk '$2 ~ /java/ {s += $1} END {printf "%.1f MB\n", s/1024}'
함정과 주의점
열 번호는 로그 포맷에 따라 달라진다. $9 = 상태코드 는 nginx 기본 combined 포맷일 때만 맞다. 로그 포맷에 $http_x_forwarded_for 나 $request_time 을 추가했다면 번호가 밀린다. 명령을 그대로 복사해 쓰기 전에 반드시 head -1 access.log 로 실제 한 줄을 보고 번호를 세어본다. 잘못된 열로 낸 통계는 틀렸다는 사실조차 드러나지 않는다.
셸 변수는 작은따옴표 안에서 보이지 않는다. awk '{print $LIMIT}' 는 셸 변수가 아니라 awk 의 $LIMIT(대개 $0)으로 해석된다. 값을 넘기려면 -v 를 쓴다.
LIMIT=500
awk -v lim="$LIMIT" '$10 > lim {print}' a.log
작은따옴표를 끊어 '"$LIMIT"' 처럼 끼워 넣는 방식은 값에 공백이나 특수문자가 있으면 깨진다. -v 가 정답이다.
-F, 로 CSV 를 파싱하면 언젠가 틀린다. 따옴표 안에 콤마가 들어간 필드("서울, 강남구")를 awk 는 두 열로 쪼갠다. 사람이 편집한 CSV 라면 조만간 반드시 이런 줄이 들어온다. 정식 CSV 는 파이썬이나 전용 도구로 읽고, awk 는 구분자가 확실히 데이터에 없는 경우에만 쓴다.
문자열 비교와 숫자 비교가 다르다. awk 는 값을 상황에 따라 문자열로 볼 수 있어, "09" < "1" 같은 결과가 나온다. 숫자로 다뤄야 하는 열은 $5 + 0 처럼 명시적으로 변환한다.
배포판마다 awk 실체가 다르다. Ubuntu/Debian 의 기본 awk 는 mawk, RHEL 계열은 gawk 다. gensub(), strftime(), asort() 같은 함수는 gawk 전용이라 우분투 서버에서 조용히 실패한다. 이런 함수를 쓸 거라면 스크립트에서 awk 대신 gawk 를 명시하고 설치 여부를 확인한다.
함께 보면 좋은 명령어
sort/uniq— awk 로 뽑은 열을 세고 순위를 매기는 짝꿍이다.| sort | uniq -c | sort -rn은 통째로 외워 둘 관용구다.sed— 줄 단위 치환과 삭제는 sed 가 간결하다. 열 계산은 awk, 문자열 치환은 sed 로 나눈다.grep— 대상 줄을 먼저 크게 줄이면 awk 처리가 빨라진다. 수 GB 로그에서는 차이가 크다.