Linux 'awk'

개요

awk는 텍스트를 훑고 변환하는 작은 언어로, 로그와 CSV/TSV, 즉석 분석에 딱 맞는다. 스트리밍 스프레드시트라고 생각하면 된다. 셸을 벗어나지 않고 행을 걸러내고, 열을 골라 재배치하고, 집계를 계산하고, 결과를 출력한다.

요약 (치트시트)

  • 실행: awk 'pattern { action }' file
  • 필드: $1, $2, …, 줄 전체: $0
  • 내장 변수:

    • FS (입력 구분자), OFS (출력 구분자)
    • RS (레코드 구분자), ORS (출력 레코드 구분자)
    • NR (전체 줄 번호), FNR (파일 내 줄 번호), NF (필드 개수)
    • FILENAME, ARGC, ARGV
  • 블록: BEGIN { … } (입력 전), END { … } (모든 입력 후)
  • CLI: -F, (FS 지정), -v k=v (변수 전달), -f prog.awk (파일 사용)

매일 쓰게 되는 핵심 패턴

열 선택과 재구성

# CSV: 1번째와 3번째 열을 TSV로 출력
awk -F, '{print $1, $3}' OFS='\t' data.csv

헤더 건너뛰고 합계/평균 계산

awk -F, 'NR>1{n++; sum+=$5} END{print "count="n,"sum="sum,"avg="sum/n}' data.csv

조건 필터 (예: status ≥ 500)

awk '$9 >= 500' access.log

그룹별 집계 (사용자별 합계)

awk -F, 'NR>1{sum[$1]+=$3} END{for (u in sum) print u,sum[u]}' OFS=, tx.csv

헤더를 고려한 여러 CSV 병합

# 첫 파일의 헤더만 남기고, 나머지는 데이터 행만 출력
awk 'FNR==1 && NR!=1{next} {print}' *.csv > merged.csv

전역 치환 (로그 레벨 등)

awk '{gsub(/WARN/, "WARNING"); print}' app.log

잘못된 행 걸러내기 (열 개수 불일치 / 숫자 아님)

awk -F, 'NF>=5 && $3 ~ /^[0-9.]+$/' data.csv

고정폭 파싱

# name = 1~10번 칼럼, age = 12~14번 (뒤쪽 공백 제거)
awk '{name=substr($0,1,10); gsub(/ +$/,"",name); age=substr($0,12,3); print name,age}' OFS=, fixed.txt

중복 줄 제거

awk '!seen[$0]++' input.txt

보기 좋게 정렬된 출력

awk -F, 'NR>1{printf "%-20s %8.2f\n", $1, $3}' data.csv

CSV의 함정 (과 해법)

CSV는 까다롭다 (따옴표, 따옴표 안의 쉼표). GNU awk (gawk) 는 FPAT으로 토큰 단위 파싱을 지원한다:

gawk -v FPAT='([^,]*)|("[^"]*")' '
  NR==1{print "user,total"; next}
  { amt = $3; gsub(/"/,"",$1); sum[$1]+=amt }
  END{for (u in sum) print u "," sum[u]}
' data.csv

완전히 견고한 CSV/JSON 처리가 필요하면 전용 도구(mlr, xsv, jq)를 고려하자. awk는 형식이 예측 가능할 때 쓴다.

날짜/시간 기법 (gawk)

# 입력: 2025-08-20T09:15:32 → 시간대별 버킷
gawk -F'[T:]' '{hour=$2; cnt[hour]++} END{for(h in cnt) printf "%02d,%d\n",h,cnt[h]}' events.txt | sort -t, -k1,1n
# epoch를 파싱해 사람이 읽는 시간으로 출력
gawk '{print strftime("%Y-%m-%d %H:%M:%S", $1)}' epochs.txt

성능 팁

  • 파이프 여러 개보다 awk 하나를 선호하라. 한 번 훑으면서 필터링과 서식을 모두 처리할 수 있다.
  • 거대한 데이터에서는 속도를 위해 로케일을 미리 지정하라:

    LC_ALL=C awk '…' bigfile
    
  • mawk는 아주 빠르지만 gawk의 일부 기능이 없다. busybox awk는 최소한만 제공한다. FPAT, asort()/asorti(), in-place 편집이 필요하면 gawk를 쓰자.

In-place 편집 (gawk)

# 치환 후 되쓰기 (gawk 확장)
gawk -i inplace '{gsub(/DEBUG/, "INFO"); print}' app.log

(이식성 있는 대안: 임시 파일에 쓴 뒤 mv.)

미니 쿡북

등장 횟수 기준 상위 N명

gawk '{c[$1]++} END{for(u in c) print c[u],u}' file | sort -nr | head

줄 번호로 두 파일 합치기 (2열 리포트)

paste ids.txt amounts.txt | awk -F'\t' '{print $1 "," $2}'

키(첫 필드) 기준 유일 행

awk -F, '!seen[$1]++' data.csv

HTTP 코드 히스토그램 (9번째 필드)

awk '{h[$9]++} END{for(k in h) print k,h[k]}' access.log | sort -k1,1n

awk를 쓰지 말아야 할 때

  • 복잡한 CSV/JSON/XML (mlr/jq/제대로 된 파서를 쓸 것).
  • RS/ORS를 조정하지 않는 한, 개행이 들어간 다중 줄 레코드.

정리: 한 문장으로 말할 수 있다면(“헤더 건너뛰고 사용자별로 3번 열 합계”), 보통 awk 하나로 쓸 수 있다.