본문 바로가기
TYLER SONGBlog
← Blog index
CS학습5 min read

awk: 줄 단위로 텍스트를 필드로 쪼개 처리하는 패턴-액션 언어

awk는 입력을 레코드(줄)와 필드(열)로 나누어 "패턴 { 액션 }" 규칙을 적용하는 텍스트 처리 언어다. 로그, CSV, 명령어 출력처럼 열 구조가 있는 텍스트에서 특정 열 추출, 조건 필터링, 합계·평균 같은 집계를 한 줄 명령으로 수행할 수 있다. 파이프라인에서 grep과 cut으로 부족한 부분을 채우는 도구로 자주 쓰인다.

By 송민성

1. 개념

awk는 텍스트를 한 줄씩 읽어 규칙에 따라 처리하는 스크립트 언어이자 명령행 도구다. 이름은 개발자인 Aho, Weinberger, Kernighan의 머리글자에서 왔다.

핵심 구조는 패턴-액션(pattern-action) 규칙이다.

text
pattern { action }
  • 입력의 각 줄을 레코드(record)라고 한다.
  • 각 레코드는 구분자로 나뉜 필드(field)로 쪼개진다. 기본 구분자는 공백과 탭이다.
  • 패턴이 참이면 액션을 실행한다. 패턴을 생략하면 모든 줄에 액션을 적용하고, 액션을 생략하면 일치하는 줄을 그대로 출력한다.

주요 내장 변수는 다음과 같다.

변수의미
$0현재 줄 전체
$1, $2, ...첫 번째, 두 번째, ... 필드
NF현재 줄의 필드 수(Number of Fields)
NR지금까지 읽은 전체 줄 번호(Number of Records)
FS입력 필드 구분자(Field Separator)
OFS출력 필드 구분자(Output Field Separator)

2. 왜 사용하는가

  • cut은 구분자가 여러 개의 공백일 때 다루기 불편하고, grep은 줄 전체만 걸러낼 뿐 열 단위 연산을 못 한다.
  • awk는 조건 필터링, 열 추출, 산술 계산, 집계를 하나의 명령으로 처리한다.
  • 별도 스크립트 파일이나 컴파일 없이 터미널에서 바로 실행할 수 있다.
  • 대부분의 Linux/macOS 환경에 기본 설치되어 있어 서버에서 추가 설치 없이 쓸 수 있다.

3. 동작 원리

awk의 실행은 세 단계로 이루어진다.

  1. BEGIN 블록: 입력을 읽기 전에 한 번 실행된다. 변수 초기화, 헤더 출력에 쓴다.
  2. 메인 루프: 입력의 각 레코드마다 FS로 필드를 나누고, 모든 pattern { action } 규칙을 위에서부터 차례로 평가한다.
  3. END 블록: 모든 입력을 처리한 뒤 한 번 실행된다. 합계, 평균 같은 최종 결과를 출력한다.

변수는 선언 없이 쓸 수 있고, 초기값은 빈 문자열이면서 숫자로는 0으로 취급된다. 그래서 sum += $3 같은 누적이 초기화 없이도 동작한다. 연관 배열(associative array)을 기본 제공하므로 count[$1]++처럼 키별 집계도 간단하다.

4. 코드 예제

먼저 실습용 파일을 만든다.

shell
cat > access.log <<'EOF' 10.0.0.1 GET /index.html 200 512 10.0.0.2 POST /api/login 401 128 10.0.0.1 GET /api/users 200 2048 10.0.0.3 GET /missing 404 64 10.0.0.2 GET /api/users 500 0 EOF

특정 열 출력:

shell
awk '{ print $1, $3 }' access.log

조건 필터링(상태 코드가 400 이상인 줄):

shell
awk '$4 >= 400' access.log

헤더와 합계 출력(BEGIN / END):

shell
awk 'BEGIN { print "총 바이트 계산" } { sum += $5 } END { print "합계:", sum, "평균:", sum / NR }' access.log

IP별 요청 수 집계(연관 배열):

shell
awk '{ count[$1]++ } END { for (ip in count) print ip, count[ip] }' access.log

구분자 지정(CSV, 쉼표 기준):

shell
printf 'name,age\nkim,30\nlee,25\n' | awk -F, 'NR > 1 { print $1 "의 나이는 " $2 }'

명령어 출력과 조합(메모리 사용량이 큰 프로세스):

shell
ps aux | awk 'NR > 1 && $4 > 1.0 { print $2, $4, $11 }'

5. 시간 복잡도 또는 성능 특성

  • awk는 입력을 처음부터 끝까지 한 번 순차적으로 읽으므로, 줄 수를 n이라 하면 기본 처리 비용은 O(n)이다. 규칙이 k개면 줄마다 규칙을 평가하므로 대략 O(n·k)다.
  • 연관 배열의 삽입과 조회는 해시 테이블 기반이라 평균적으로 O(1)이다.
  • 입력을 스트림으로 처리하므로 파일 전체를 메모리에 올리지 않는다. 단, 배열에 모든 키를 쌓는 경우 고유 키 수만큼 메모리를 쓴다.
  • 구현체(gawk, mawk, BWK awk)마다 속도 차이가 있으므로, 대용량 처리에서는 실제 환경에서 측정하는 것이 정확하다.

6. 실무 사용 사례

  • 로그 분석: 상태 코드별 요청 수, 응답 시간 평균, 특정 IP의 트래픽 합계 계산
  • CSV/TSV 가공: 특정 열 추출, 조건에 맞는 행 필터링
  • 명령어 출력 정리: df -h, ps, docker ps 같은 출력에서 필요한 열만 뽑아 다른 명령에 전달
  • 배포/운영 스크립트: 한 줄짜리 집계를 셸 스크립트 안에서 간결하게 처리
shell
# 5xx 에러 비율 계산 awk '$4 >= 500 { err++ } END { printf "5xx 비율: %.1f%%\n", err / NR * 100 }' access.log

7. 주의할 점

  • 셸 따옴표: awk 프로그램은 작은따옴표('...')로 감싸야 $1이 셸 변수로 해석되지 않는다. 셸 변수를 넘기려면 -v name="$value" 옵션을 쓴다.
  • 필드 인덱스는 1부터 시작한다. $0은 줄 전체다.
  • CSV의 따옴표 처리: 필드 안에 쉼표가 있는 CSV("a,b",c)는 -F,로 올바르게 나눌 수 없다. 이런 데이터는 전용 파서를 쓰는 편이 안전하다.
  • 문자열 vs 숫자 비교: 입력 필드가 숫자처럼 보이면 숫자로 비교되지만, 그렇지 않으면 문자열 비교가 된다. 의도하지 않은 결과가 나오면 $4 + 0처럼 숫자로 변환해 확인한다.
  • 구현체 차이: gawk 전용 기능(asort, strftime 등)은 macOS 기본 awk나 mawk에서 동작하지 않을 수 있다.
  • 배열 순회 순서: for (k in arr)의 순회 순서는 보장되지 않는다. 정렬이 필요하면 | sort로 넘긴다.

8. 핵심 정리

  • awk는 입력을 레코드(줄)와 필드(열)로 나누고 pattern { action } 규칙을 적용하는 도구다.
  • $0, $1~$NF, NR, NF, FS를 알면 대부분의 작업을 처리할 수 있다.
  • BEGIN은 시작 전, END는 종료 후에 한 번 실행되어 초기화와 집계 출력에 쓴다.
  • 연관 배열로 키별 카운트와 합계를 간단히 구할 수 있다.
  • 처리 비용은 입력 크기에 비례(O(n))하며 스트림 방식이라 메모리 부담이 적다.
  • 셸 따옴표, CSV 따옴표 필드, 구현체 차이를 주의해야 한다.