Linux awk 명령어: 텍스트 처리

LinuxBeginner
지금 연습하기

소개

AWK 를 활용한 텍스트 처리의 세계에 오신 것을 환영합니다. 이번 실습에서는 시스템 관리자와 데이터 분석가에게 필수적인 작업인 로그 파일 분석을 위해 awk 명령어를 사용하는 방법을 배웁니다. AWK 는 Linux 에서 구조화된 텍스트 데이터를 효율적으로 추출, 필터링 및 변환할 수 있게 해주는 강력한 도구입니다.

여러분이 잠재적인 보안 위협과 성능 문제를 파악하기 위해 서버 로그를 분석해야 하는 주니어 시스템 관리자라고 가정해 봅시다. awk 명령어는 대규모 로그 파일을 빠르게 훑어보고 의미 있는 통찰을 추출하는 데 가장 핵심적인 도구가 될 것입니다.

로그 파일 살펴보기

먼저 실습 전반에 걸쳐 분석할 샘플 로그 파일의 내용을 살펴보겠습니다. 이 파일에는 시뮬레이션된 서버 액세스 로그가 포함되어 있습니다.

먼저 프로젝트 디렉토리로 이동합니다:

cd ~/project

이제 로그 파일의 앞부분 몇 줄을 확인해 보겠습니다:

head -n 5 server_logs.txt

다음과 유사한 출력이 나타날 것입니다:

2023-08-01 08:15:23 192.168.1.100 GET /index.html 200
2023-08-01 08:16:45 192.168.1.101 GET /about.html 200
2023-08-01 08:17:30 192.168.1.102 POST /login.php 302
2023-08-01 08:18:12 192.168.1.103 GET /products.html 404
2023-08-01 08:19:05 192.168.1.104 GET /services.html 200

이 로그 파일에는 날짜와 시간, IP 주소, HTTP 메서드, 요청된 리소스, 상태 코드 등 서버 요청에 대한 정보가 포함되어 있습니다.

기본 AWK 사용법 - 특정 필드 출력하기

로그 파일의 구조를 확인했으니, 이제 AWK 를 사용하여 특정 정보를 추출해 보겠습니다. 기본적으로 AWK 는 공백을 기준으로 각 줄을 필드로 나눕니다. 이러한 필드는 $1, $2와 같이 참조할 수 있으며, $1은 첫 번째 필드, $2는 두 번째 필드를 의미합니다.

로그 파일에서 IP 주소 (세 번째 필드) 를 추출해 보겠습니다:

awk '{print $3}' server_logs.txt | head -n 5

다음과 유사한 출력이 나타날 것입니다:

192.168.1.100
192.168.1.101
192.168.1.102
192.168.1.103
192.168.1.104

이 명령어에서:

  • awk '{print $3}'는 AWK 에게 각 줄의 세 번째 필드를 출력하도록 지시합니다.
  • 파이프 (|) 를 사용하여 출력을 head -n 5로 전달함으로써 처음 5 줄만 표시하도록 제한했습니다.

이제 IP 주소와 요청된 리소스를 모두 출력해 보겠습니다:

awk '{print $3, $5}' server_logs.txt | head -n 5

출력:

192.168.1.100 /index.html
192.168.1.101 /about.html
192.168.1.102 /login.php
192.168.1.103 /products.html
192.168.1.104 /services.html

여기서는 각 줄의 세 번째 필드 (IP 주소) 와 다섯 번째 필드 (요청된 리소스) 를 출력하고 있습니다.

로그 항목 필터링

AWK 의 강점 중 하나는 조건에 따라 데이터를 필터링하는 능력입니다. 이 기능을 사용하여 로그 파일에서 모든 POST 요청을 찾아보겠습니다. POST 요청은 GET 요청보다 보안상 더 민감할 수 있기 때문입니다.

다음 명령어를 실행하세요:

awk '$4 == "POST" {print $0}' server_logs.txt

샘플 파일에 5,000 개의 로그 항목이 포함되어 있으므로 이 명령어는 수백 줄을 출력할 수 있습니다. 학습 중에 관리 가능한 샘플만 확인하려면 | head -n 10을 추가하세요:

awk '$4 == "POST" {print $0}' server_logs.txt | head -n 10

검증 단계에서는 일반 awk 명령어도 허용되므로, 출력을 읽기 편한 버전을 사용하시면 됩니다.

AWK 필터링이 어떻게 작동하는지 이해하기 위해 명령어 구문을 분석해 보겠습니다:

  1. $4 == "POST" - 이는 AWK 가 각 줄에 대해 평가하는 패턴 또는 조건입니다:

    • $4는 현재 줄의 네 번째 필드를 의미합니다 (로그 파일에서는 HTTP 메서드).
    • ==는 두 값이 같은지 확인하는 동등 연산자입니다.
    • "POST"는 비교 대상이 되는 문자열입니다.
  2. {print $0} - 조건이 참일 때 AWK 가 수행하는 작업입니다:

    • 중괄호 {}는 작업을 감쌉니다.
    • print는 텍스트를 출력하는 명령어입니다.
    • $0은 현재 줄 전체 (모든 필드) 를 나타냅니다.

명령어 구조는 조건 {작업}이라는 AWK 패턴을 따릅니다. AWK 는 각 줄을 읽고 조건이 참이면 작업을 수행합니다. (앞선 예제처럼) 조건이 지정되지 않으면 모든 줄에 대해 작업이 수행됩니다.

다음과 유사한 출력이 나타날 것입니다:

2023-08-01 08:17:30 192.168.1.102 POST /login.php 302
2023-08-01 09:23:45 192.168.1.110 POST /submit_form.php 200
2023-08-01 10:45:12 192.168.1.115 POST /upload.php 500

이제 404(Not Found) 상태를 반환한 모든 요청을 찾아보겠습니다:

awk '$6 == "404" {print $1, $2, $5}' server_logs.txt

이 명령어는 동일한 패턴을 따르지만 값만 다릅니다:

  • 조건 $6 == "404"는 여섯 번째 필드 (상태 코드) 가 404 와 같은지 확인합니다.
  • 작업 {print $1, $2, $5}는 특정 필드만 출력합니다:
    • $1 - 첫 번째 필드 (날짜)
    • $2 - 두 번째 필드 (시간)
    • $5 - 다섯 번째 필드 (요청된 리소스)

이처럼 선택적 출력을 통해 필요한 정보에만 집중할 수 있습니다.

출력:

2023-08-01 08:18:12 /products.html
2023-08-01 09:30:18 /nonexistent.html
2023-08-01 11:05:30 /missing_page.html

논리 연산자를 사용하여 여러 조건을 결합할 수도 있습니다:

  • && - AND (두 조건 모두 참이어야 함)
  • || - OR (최소한 하나의 조건이 참이어야 함)
  • ! - NOT (조건을 부정)

예를 들어, 오류 (상태 코드 >= 400) 를 발생시킨 모든 POST 요청을 찾으려면 다음과 같이 합니다:

awk '$4 == "POST" && $6 >= 400 {print $0}' server_logs.txt

이러한 필터를 사용하면 서버 로그에서 잠재적인 문제나 의심스러운 활동을 빠르게 식별할 수 있습니다.

데이터 집계 및 요약

AWK 는 발생 횟수를 세고 데이터를 요약하는 데 탁월합니다. 이를 사용하여 각 HTTP 상태 코드별 요청 횟수를 계산해 보겠습니다.

다음 명령어를 실행하세요:

awk '{count[$6]++} END {for (code in count) print code, count[code]}' server_logs.txt | sort -n

이 명령어는 다소 복잡하므로 단계별로 분석해 보겠습니다:

  1. {count[$6]++} - 각 줄에 대해 수행되는 주요 작업입니다:

    • count는 우리가 생성하는 배열 (연관 배열 또는 딕셔너리) 입니다.
    • [$6]은 6 번째 필드 (상태 코드) 의 값을 배열 인덱스/키로 사용합니다.
    • ++는 증가 연산자로, 현재 값에 1 을 더합니다.
    • 즉, 각 줄마다 발견된 특정 상태 코드에 대한 카운터를 증가시킵니다.
  2. END {for (code in count) print code, count[code]} - 모든 줄을 처리한 후 실행됩니다:

    • END는 입력의 끝을 나타내는 특수 패턴입니다.
    • {...}는 모든 입력이 처리된 후 수행할 작업을 포함합니다.
    • for (code in count)count 배열의 모든 키를 반복하는 루프입니다.
    • print code, count[code]는 각 상태 코드와 그 횟수를 출력합니다.
  3. | sort -n - 출력을 sort 명령어로 파이프하여 숫자 순으로 정렬합니다.

AWK 가 count[$6]++와 같은 배열을 처리할 때 자동으로 수행하는 작업은 다음과 같습니다:

  • 배열이 존재하지 않으면 생성합니다.
  • 키가 존재하지 않으면 값을 0 으로 하는 새 요소를 생성합니다.
  • 그 후 값을 1 만큼 증가시킵니다.

다음과 유사한 출력이 나타날 것입니다:

200 3562
301 45
302 78
304 112
400 23
403 8
404 89
500 15

이 요약은 로그 파일 내 상태 코드의 분포를 빠르게 보여줍니다.

이제 가장 자주 액세스된 상위 5 개 리소스를 찾아보겠습니다:

awk '{count[$5]++} END {for (resource in count) print count[resource], resource}' server_logs.txt | sort -rn | head -n 5

이 명령어는 몇 가지 변경 사항을 제외하고는 이전과 유사한 패턴을 따릅니다:

  1. {count[$5]++} - 5 번째 필드 (요청된 리소스) 의 발생 횟수를 셉니다.
  2. END {for (resource in count) print count[resource], resource} - 모든 줄을 처리한 후:
    • 횟수를 먼저 출력하고 리소스를 뒤에 출력합니다.
    • 이 순서 변경은 횟수를 기준으로 한 숫자 정렬을 용이하게 합니다.
  3. | sort -rn - 역순으로 숫자 정렬합니다 (가장 높은 횟수가 먼저 나옴).
  4. | head -n 5 - 출력을 상위 5 줄로 제한합니다.

출력:

1823 /index.html
956 /about.html
743 /products.html
512 /services.html
298 /contact.html

이러한 AWK 명령어는 집계를 위해 배열을 사용하는 강력함을 보여줍니다. 이 패턴을 응용하여 데이터의 어떤 필드나 필드 조합이라도 집계할 수 있습니다.

예를 들어, IP 주소별 요청 횟수를 계산하려면 다음과 같이 합니다:

awk '{count[$3]++} END {for (ip in count) print ip, count[ip]}' server_logs.txt

메서드와 상태 코드별로 요청을 집계하려면 다음과 같이 합니다:

awk '{key=$4"-"$6; count[key]++} END {for (k in count) print k, count[k]}' server_logs.txt

이러한 요약 정보는 트래픽 패턴을 이해하고 서버에서 인기 있는 (또는 문제가 되는) 리소스를 식별하는 데 도움이 됩니다.

간단한 보고서 생성

마지막 작업으로 로그 파일의 핵심 정보를 요약하는 간단한 HTML 보고서를 만들어 보겠습니다. 이 복잡한 작업을 위해 별도의 파일에 저장된 AWK 스크립트를 사용합니다.

이 단계는 앞서 배운 여러 AWK 개념을 결합합니다:

  • total++와 같은 카운터
  • ip_count[$3]++와 같은 배열
  • 최종 요약을 출력하는 END 블록

스크립트가 길어 보일 수 있지만, 한 번에 한 블록씩 집중해 보세요. 실행하기 전에 전체 파일을 암기할 필요는 없습니다.

먼저 log_report.awk라는 파일을 만들고 다음 내용을 입력하세요:

팁: 아래 내용을 복사하여 터미널에 붙여넣으면 파일을 생성할 수 있습니다.

cat << 'EOF' > log_report.awk
BEGIN {
    print "<html><body>"
    print "<h1>Server Log Summary</h1>"
    total = 0
    errors = 0
}

{
    total++
    if ($6 >= 400) errors++
    ip_count[$3]++
    resource_count[$5]++
}

END {
    print "<p>Total requests: " total "</p>"
    print "<p>Error rate: " (errors/total) * 100 "%</p>"
    
    print "<h2>Top 5 IP Addresses</h2>"
    print "<ul>"
    for (ip in ip_count) {
        top_ips[ip] = ip_count[ip]
    }
    n = asort(top_ips, sorted_ips, "@val_num_desc")
    for (i = 1; i <= 5 && i <= n; i++) {
        for (ip in ip_count) {
            if (ip_count[ip] == sorted_ips[i]) {
                print "<li>" ip ": " ip_count[ip] " requests</li>"
                break
            }
        }
    }
    print "</ul>"
    
    print "<h2>Top 5 Requested Resources</h2>"
    print "<ul>"
    for (resource in resource_count) {
        top_resources[resource] = resource_count[resource]
    }
    n = asort(top_resources, sorted_resources, "@val_num_desc")
    for (i = 1; i <= 5 && i <= n; i++) {
        for (resource in resource_count) {
            if (resource_count[resource] == sorted_resources[i]) {
                print "<li>" resource ": " resource_count[resource] " requests</li>"
                break
            }
        }
    }
    print "</ul>"
    
    print "</body></html>"
}
EOF

이 AWK 스크립트를 섹션별로 이해해 봅시다:

  1. BEGIN 블록: 입력 줄을 처리하기 전에 실행됩니다.

    BEGIN {
        print "<html><body>"  ## HTML 구조 시작
        print "<h1>Server Log Summary</h1>"
        total = 0  ## 총 요청 카운터 초기화
        errors = 0  ## 오류 요청 카운터 초기화
    }
    
  2. 메인 처리 블록: 입력 파일의 각 줄에 대해 실행됩니다.

    {
        total++  ## 총 요청 카운터 증가
        if ($6 >= 400) errors++  ## 오류 응답 카운트 (상태 코드 >= 400)
        ip_count[$3]++  ## IP 주소별 요청 카운트 (필드 3)
        resource_count[$5]++  ## 리소스별 요청 카운트 (필드 5)
    }
    
  3. END 블록: 모든 입력 줄을 처리한 후 실행됩니다.

    END {
        ## 요약 통계 출력
        print "<p>Total requests: " total "</p>"
        print "<p>Error rate: " (errors/total) * 100 "%</p>"
    
        ## 상위 5개 IP 주소 처리 및 출력
        ## ...
    
        ## 상위 5개 요청 리소스 처리 및 출력
        ## ...
    
        print "</body></html>"  ## HTML 구조 종료
    }
    

진행하기 전에 전체 흐름을 확인하세요:

  1. BEGIN은 HTML 여는 태그를 출력하고 카운터를 초기화합니다.
  2. 중간 블록은 각 로그 줄을 처리하고 합계를 업데이트합니다.
  3. END는 모든 줄이 분석된 후 최종 보고서를 출력합니다.

상위 IP 정렬 로직을 살펴보겠습니다 (리소스 섹션도 동일하게 작동합니다):

## 정렬을 위해 카운트를 새 배열로 복사
for (ip in ip_count) {
    top_ips[ip] = ip_count[ip]
}

## 값을 기준으로 내림차순 정렬
n = asort(top_ips, sorted_ips, "@val_num_desc")

## 상위 5개 항목 출력
for (i = 1; i <= 5 && i <= n; i++) {
    ## 이 카운트와 일치하는 원래 IP 찾기
    for (ip in ip_count) {
        if (ip_count[ip] == sorted_ips[i]) {
            print "<li>" ip ": " ip_count[ip] " requests</li>"
            break
        }
    }
}

이 스크립트에서:

  • asort() 함수는 배열을 정렬합니다.
  • "@val_num_desc"는 값을 기준으로 숫자 내림차순 정렬을 수행하도록 지시하는 특수 인수입니다.
  • 중첩 루프는 상위 5 개 항목을 찾아 출력합니다.

중첩 루프는 다음과 같이 생각할 수 있습니다:

  • 첫 번째 루프는 어떤 카운트가 상위 5 개에 속하는지 결정합니다.
  • 두 번째 루프는 각 카운트를 생성한 IP 주소나 리소스를 찾습니다.

이 조회 패턴은 이전 단계보다 고급 수준이므로, 실습 중 처음으로 실제 스크립팅을 하는 것처럼 느껴지는 것이 정상입니다.

이제 AWK 스크립트를 실행하여 보고서를 생성해 보겠습니다:

awk -f log_report.awk server_logs.txt > log_report.html

-f 옵션은 AWK 에게 지정된 파일에서 스크립트를 읽도록 지시합니다:

  • -f log_report.awk - log_report.awk 파일에서 AWK 스크립트를 읽습니다.
  • server_logs.txt - 이 파일을 스크립트를 사용하여 처리합니다.
  • > log_report.html - 출력을 log_report.html 파일로 리다이렉션합니다.

cat 명령어를 사용하여 보고서 내용을 확인할 수 있습니다:

cat log_report.html

HTML 출력을 터미널에서 읽기 어렵다면 앞부분만 미리 확인해 보세요:

head -n 15 log_report.html

이 보고서는 총 요청 수, 오류율, 상위 5 개 IP 주소 및 상위 5 개 요청 리소스에 대한 요약을 제공합니다. 실제 환경에서는 이 HTML 파일을 웹 브라우저에서 열어 서식이 지정된 형태로 볼 수 있습니다.

이 스크립트에서 사용한 접근 방식은 AWK 가 더 복잡한 데이터 분석 작업에 어떻게 사용될 수 있는지 보여줍니다. 이 스크립트를 확장하여 추가 통계나 특정 요구 사항에 맞는 다양한 시각화를 포함할 수 있습니다.

요약

축하합니다! 로그 분석을 위한 AWK 명령어 실습을 완료했습니다. 배운 내용을 요약해 보겠습니다:

  1. 기본 AWK 사용법: 구조화된 텍스트 파일에서 특정 필드 출력하기.
  2. 데이터 필터링: AWK 의 조건을 사용하여 특정 로그 항목 선택하기.
  3. 집계 및 요약: AWK 를 사용하여 로그 데이터에서 통계 생성하기.
  4. 보고서 생성: 서식이 지정된 보고서를 생성하기 위해 더 복잡한 AWK 스크립트 작성하기.

이러한 기술은 향후 시스템 관리자나 데이터 분석가로서 로그 파일을 분석하고, 데이터를 처리하며, 보고서를 생성하는 데 매우 귀중한 자산이 될 것입니다.

이번 실습에서 다루지 않은 추가적인 AWK 매개변수와 기능은 다음과 같습니다:

  • -F: 공백 이외의 필드 구분 기호를 지정합니다.
  • -v: 변수에 값을 할당합니다.
  • NR: 현재 레코드 번호를 나타내는 내장 변수입니다.
  • NF: 현재 레코드의 필드 수를 나타내는 내장 변수입니다.
  • BEGINEND 블록: 초기화 및 종료를 위한 특수 패턴입니다.
  • 내장 함수: 수학 함수, 문자열 함수 등.

AWK 를 마스터하려면 연습이 핵심입니다. 이 실습의 명령어와 스크립트를 수정하여 로그 파일의 다른 측면을 분석하거나 다른 유형의 구조화된 텍스트 데이터를 처리해 보세요.