동작하는 코드
예제 1: awk 패턴/액션 기본 구조
# 연습용 로그 파일 만들기
cat > app.log << 'EOF'
2024-01-15 10:30:01 INFO 사용자 로그인: user001
2024-01-15 10:30:05 ERROR 데이터베이스 연결 실패
2024-01-15 10:30:10 INFO 파일 업로드 완료: report.pdf
2024-01-15 10:31:00 WARN 메모리 사용량 80% 초과
2024-01-15 10:31:05 ERROR 파일 저장 실패: permission denied
2024-01-15 10:32:00 INFO 사용자 로그아웃: user001
EOF
awk 기본 사용:
# 3번째 열(로그 레벨)만 출력
awk '{ print $3 }' app.log
출력:
INFO
ERROR
INFO
WARN
ERROR
INFO
# ERROR인 행만 출력
awk '$3 == "ERROR"' app.log
출력:
2024-01-15 10:30:05 ERROR 데이터베이스 연결 실패
2024-01-15 10:31:05 ERROR 파일 저장 실패: permission denied
예제 2: awk BEGIN과 END
# 처음에 헤더, 끝에 요약 출력
awk '
BEGIN { print "=== 오류 목록 ===" }
$3 == "ERROR" { print NR"번째 줄:", $0 }
END { print "=== 검색 완료 ===" }
' app.log
출력:
=== 오류 목록 ===
2번째 줄: 2024-01-15 10:30:05 ERROR 데이터베이스 연결 실패
5번째 줄: 2024-01-15 10:31:05 ERROR 파일 저장 실패: permission denied
=== 검색 완료 ===
예제 3: sed 텍스트 치환
echo "Hello World" | sed 's/World/Terminal/'
출력:
Hello Terminal
# 파일에서 치환 (파일 내용은 변경 안 됨, stdout으로 출력)
sed 's/INFO/정보/g' app.log | head -3
출력:
2024-01-15 10:30:01 정보 사용자 로그인: user001
2024-01-15 10:30:05 ERROR 데이터베이스 연결 실패
2024-01-15 10:30:10 정보 파일 업로드 완료: report.pdf
직접 수정하기
awk 심화: 집계와 계산
# 로그 레벨별 개수 집계
awk '{ count[$3]++ } END { for (level in count) print level":", count[level] }' app.log
출력:
INFO: 3
ERROR: 2
WARN: 1
# CSV 데이터 처리
cat > sales.csv << 'EOF'
이름,상품,금액
홍길동,노트북,1200000
김철수,마우스,45000
이영희,키보드,89000
박민수,노트북,1150000
최지원,모니터,350000
EOF
# 금액 합계 계산
awk -F',' 'NR > 1 { sum += $3 } END { printf "총 매출: %d원\n", sum }' sales.csv
출력:
총 매출: 2834000원
# 100만원 이상 상품만 출력
awk -F',' 'NR > 1 && $3 >= 1000000 { print $1, $2, $3 }' sales.csv
출력:
홍길동 노트북 1200000
박민수 노트북 1150000
awk NF와 NR 활용
# NF: 현재 줄의 필드 수
echo "a b c d" | awk '{ print "필드 수:", NF }'
# 필드 수: 4
# 마지막 필드
echo "a b c d" | awk '{ print "마지막:", $NF }'
# 마지막: d
# NR: 줄 번호
awk 'NR >= 2 && NR <= 4' app.log
출력 (2~4번째 줄):
2024-01-15 10:30:05 ERROR 데이터베이스 연결 실패
2024-01-15 10:30:10 INFO 파일 업로드 완료: report.pdf
2024-01-15 10:31:00 WARN 메모리 사용량 80% 초과
sed 심화
# 기본 치환: s/찾기/바꾸기/플래그
echo "apple apple apple" | sed 's/apple/banana/' # 첫 번째만
echo "apple apple apple" | sed 's/apple/banana/g' # 모두 (global)
echo "APPLE" | sed 's/apple/banana/i' # 대소문자 무시
# 특정 줄만 치환 (2번째 줄)
sed '2s/ERROR/에러/' app.log
# 특정 줄 삭제
sed '1d' app.log # 1번째 줄 삭제
sed '/ERROR/d' app.log # ERROR 포함 줄 삭제
sed '2,4d' app.log # 2~4번째 줄 삭제
# 특정 줄만 출력 (-n과 p 조합)
sed -n '2,4p' app.log # 2~4번째 줄만 출력
sed -n '/ERROR/p' app.log # ERROR 포함 줄만 출력
# 줄 앞에 텍스트 추가
sed 's/^/[LOG] /' app.log
파일 직접 수정 (-i 옵션):
# 백업 없이 직접 수정 (주의!)
sed -i 's/ERROR/에러/g' app.log
# 백업 파일 만들면서 수정 (원본은 .bak으로 보존)
sed -i.bak 's/INFO/정보/g' app.log
ls
# app.log app.log.bak
"왜?" — 실전 로그 분석 파이프라인
시나리오: 아파치 웹 서버 로그 분석
아파치 접속 로그 형식:
192.168.1.1 - - [15/Jan/2024:10:30:01] "GET /index.html HTTP/1.1" 200 1234
192.168.1.2 - - [15/Jan/2024:10:30:05] "POST /api/login HTTP/1.1" 401 567
10.0.0.1 - - [15/Jan/2024:10:30:10] "GET /admin HTTP/1.1" 404 234
# IP별 접속 횟수 (상위 10개)
awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10
# 404 에러 URL만
awk '$9 == "404" { print $7 }' access.log
# 시간대별 요청 수
awk '{ print substr($4, 2, 14) }' access.log | cut -d: -f2 | sort | uniq -c
# 특정 IP 차단 (iptables에 추가할 목록)
awk '$9 == "401" { print $1 }' access.log | sort | uniq -c | sort -rn | awk '$1 > 100 { print $2 }'
시나리오: CSV 데이터 변환
# CSV를 TSV로 변환 (구분자 변경)
sed 's/,/\t/g' data.csv > data.tsv
# 헤더 제거
sed '1d' data.csv > data-no-header.csv
# 특정 열 제거 (2번째 열)
awk -F',' 'BEGIN{OFS=","} { $2=""; print }' data.csv
# 열 순서 변경 (3,1,2 순으로)
awk -F',' 'BEGIN{OFS=","} { print $3, $1, $2 }' data.csv
흔한 실수
실수 1: awk와 grep 역할 혼동
# grep: 패턴이 있는 줄 찾기
grep "ERROR" app.log
# awk: 특정 필드가 일치하는 줄 찾기
awk '$3 == "ERROR"' app.log # 3번째 필드가 정확히 "ERROR"인 것만
# 차이: grep은 줄 전체에서, awk는 필드 기준
grep "ERROR" app.log # "NOT_AN_ERROR"도 매칭됨
awk '$3 == "ERROR"' app.log # 정확히 3번째 필드가 "ERROR"인 것만
실수 2: sed의 -i 옵션 주의
# macOS에서 -i 뒤에 반드시 확장자나 '' 필요
sed -i '' 's/old/new/g' file.txt # macOS
sed -i 's/old/new/g' file.txt # Linux
# 실수 방지: 먼저 stdout으로 확인
sed 's/old/new/g' file.txt | head -5
# 결과가 맞으면 -i로 실제 수정
실수 3: awk에서 문자열 비교 vs 숫자 비교
# 문자열 비교 (== 사용)
awk '$3 == "ERROR"' app.log # 올바름
# 숫자 비교 (산술 비교)
awk '$2 > 100' numbers.csv # 올바름
# 문자열로 저장된 숫자 비교 주의
awk '$2 > "100"' numbers.csv # 사전순 비교! "9" > "100"이 됨
awk '$2 + 0 > 100' numbers.csv # 숫자로 강제 변환 후 비교
심화 학습
awk 다중 파일 처리와 FILENAME
# 여러 파일 처리 시 현재 파일명 출력
awk '{ print FILENAME, $0 }' *.log
# 파일별 통계
awk '{ count[FILENAME]++ } END { for (f in count) print f, count[f] }' *.log
# FNR: 파일 내 줄 번호 (NR은 전체 줄 번호)
awk '{ print FILENAME, FNR, $0 }' file1.txt file2.txt
sed 정규식 활용
# 날짜 형식 변환 (YYYY-MM-DD → DD/MM/YYYY)
echo "2024-01-15" | sed 's/\([0-9]*\)-\([0-9]*\)-\([0-9]*\)/\3\/\2\/\1/'
# 15/01/2024
# 빈 줄 제거
sed '/^$/d' file.txt
# 주석 줄 제거 (# 으로 시작하는 줄)
sed '/^#/d' config.txt
# 앞뒤 공백 제거
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' file.txt
# HTML 태그 제거
sed 's/<[^>]*>//g' page.html
실전: 설정 파일 자동 수정 스크립트
#!/bin/bash
set -euo pipefail
CONFIG="app.conf"
# 특정 설정값 변경
update_config() {
local key="$1"
local value="$2"
# 이미 있으면 교체, 없으면 추가
if grep -q "^${key}=" "$CONFIG"; then
sed -i.bak "s/^${key}=.*/${key}=${value}/" "$CONFIG"
else
echo "${key}=${value}" >> "$CONFIG"
fi
}
update_config "DB_HOST" "localhost"
update_config "DB_PORT" "5432"
update_config "DEBUG" "false"
echo "설정 업데이트 완료"
- 로그 파일을 만들고
awk '$3 == "ERROR"' 파일명으로 에러 줄만 출력해보세요. awk '{ count[$3]++ } END { for (k in count) print k, count[k] }' 파일명으로 레벨별 개수를 집계하세요.sed 's/INFO/정보/g' 파일명으로 텍스트를 치환해보세요.sed -n '/ERROR/p' 파일명으로 ERROR 포함 줄만 출력해보세요.- CSV 파일을 만들고
awk -F',' 'NR > 1 { sum += $2 } END { print "합계:", sum }' 파일명으로 두 번째 열의 합을 구해보세요.
Q1. awk 'BEGIN { print "시작" } $3 == "ERROR" { count++ } END { print "에러:", count }' log.txt에서 각 블록의 실행 시점은?
- A) BEGIN=매 줄마다, $3=="ERROR"=파일 처음, END=파일 끝
- B) BEGIN=파일 처리 전, $3=="ERROR"=조건 맞는 줄마다, END=파일 처리 후
- C) BEGIN=조건 처음, $3=="ERROR"=파일 처음, END=파일 끝
- D) BEGIN=항상, $3=="ERROR"=ERROR 파일에서, END=항상