Linux awk 명령어: 텍스트 처리

LinuxBeginner
지금 연습하기

소개

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

당신이 보안 위협과 성능 문제를 식별하기 위해 서버 로그를 분석하는 업무를 맡은 주니어 시스템 관리자라고 가정해 봅시다. awk 명령어는 대규모 로그 파일을 신속하게 훑어보고 의미 있는 통찰력을 추출할 수 있게 해주는 핵심 도구가 될 것입니다.

이 과정은 학습과 실습을 돕기 위해 단계별 지침을 제공하는 가이드 랩 (Guided Lab) 입니다. 각 단계를 완료하고 실무 경험을 쌓으려면 지침을 주의 깊게 따르십시오. 과거 데이터에 따르면 이 랩은 초급 수준이며 93%의 수료율을 기록하고 있습니다. 학습자들로부터 96%의 긍정적인 평가를 받았습니다.

로그 파일 살펴보기

먼저 샘플 로그 파일의 내용을 확인하는 것부터 시작해 보겠습니다. 이 파일에는 이번 실습 전체에서 분석할 가상의 서버 접속 로그가 포함되어 있습니다.

먼저 프로젝트 디렉터리로 이동합니다.

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

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

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

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

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

명령어 구조는 AWK 의 기본 패턴인 condition {action}을 따릅니다. 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 스크립트를 사용하겠습니다.

먼저, 다음 내용을 포함하는 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 구조 종료
    }

상위 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 개 항목을 찾아 출력합니다.

이제 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

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

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

요약

축하합니다! 로그 분석을 위한 AWK 명령어 사용 실습을 완료하셨습니다. 배운 내용을 정리해 보겠습니다.

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

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

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

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

연습이 AWK 를 마스터하는 열쇠임을 기억하세요. 이 실습의 명령어와 스크립트를 수정하여 로그 파일의 다른 측면을 분석하거나 다른 유형의 구조화된 텍스트 데이터를 처리해 보시기 바랍니다.

참고 자료