Devin.KR
로그인

리눅스 awk 명령어 사용법 - 로그 분석과 집계 실전 예제 정리

개발자 조회 2

이 명령어를 언제 쓰는가

트래픽이 갑자기 튀었을 때 어느 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. 디스크 사용률이 임계치를 넘은 파티션만 알린다. 감시 스크립트에 그대로 넣는 형태다. $587% 처럼 퍼센트 기호가 붙어 있으므로 +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 로그에서는 차이가 크다.