grep과 정규표현식 조합
배운 내용을 실전에 적용합니다. 이 절에서는 실무에서 실제로 마주치는 다섯 가지 상황을 다룹니다. Apache 로그 분석, 설정 파일 정제, 사용자 검색, 소스코드 분석, 데이터 유효성 검사입니다.
실습 1: Apache 로그에서 HTTP 상태 코드 추출
access.log에서 특정 HTTP 상태 코드를 정규표현식으로 정확히 잡습니다.
# 큰 규모 로그 시뮬레이션cat > /tmp/apache.log << 'EOF'10.0.0.1 - - [24/Apr/2026:10:00:00 +0900] "GET /index.html HTTP/1.1" 200 204810.0.0.2 - - [24/Apr/2026:10:00:01 +0900] "POST /api/login HTTP/1.1" 200 51210.0.0.3 - - [24/Apr/2026:10:00:02 +0900] "GET /api/users HTTP/1.1" 401 25610.0.0.1 - - [24/Apr/2026:10:00:03 +0900] "GET /admin HTTP/1.1" 403 12810.0.0.4 - - [24/Apr/2026:10:00:04 +0900] "GET /missing HTTP/1.1" 404 6410.0.0.5 - - [24/Apr/2026:10:00:05 +0900] "POST /api/data HTTP/1.1" 500 51210.0.0.2 - - [24/Apr/2026:10:00:06 +0900] "GET /index.html HTTP/1.1" 200 204810.0.0.5 - - [24/Apr/2026:10:00:07 +0900] "GET /api/items HTTP/1.1" 500 12810.0.0.3 - - [24/Apr/2026:10:00:08 +0900] "DELETE /api/users/1 HTTP/1.1" 204 010.0.0.1 - - [24/Apr/2026:10:00:09 +0900] "GET /api/health HTTP/1.1" 200 32EOF
# 4xx 오류 전체 (클라이언트 오류)grep -E '" [45][0-9]{2} ' /tmp/apache.log
10.0.0.3 - - [24/Apr/2026:10:00:02 +0900] "GET /api/users HTTP/1.1" 401 256
10.0.0.1 - - [24/Apr/2026:10:00:03 +0900] "GET /admin HTTP/1.1" 403 128
10.0.0.4 - - [24/Apr/2026:10:00:04 +0900] "GET /missing HTTP/1.1" 404 64
10.0.0.5 - - [24/Apr/2026:10:00:05 +0900] "POST /api/data HTTP/1.1" 500 512
10.0.0.5 - - [24/Apr/2026:10:00:07 +0900] "GET /api/items HTTP/1.1" 500 128
패턴 '" [45][0-9]{2} '는 큰따옴표 뒤 공백, 4 또는 5로 시작하는 세 자리 숫자, 뒤 공백을 매칭합니다. 상태 코드 앞뒤의 공백을 포함해 잘못된 매칭을 방지합니다.
# 5xx 서버 오류만grep -E '" 5[0-9]{2} ' /tmp/apache.log
10.0.0.5 - - [24/Apr/2026:10:00:05 +0900] "POST /api/data HTTP/1.1" 500 512
10.0.0.5 - - [24/Apr/2026:10:00:07 +0900] "GET /api/items HTTP/1.1" 500 128
# 상태 코드별 집계grep -oE '" [0-9]{3} ' /tmp/apache.log | tr -d '" ' | sort | uniq -c | sort -rn
4 200
2 500
1 404
1 403
1 401
1 204
-o 옵션은 매칭된 부분만 출력합니다. 전체 줄이 아니라 상태 코드 부분만 잘라낼 수 있습니다.
실습 2: 설정 파일에서 실제 설정만 출력
주석과 빈 줄을 제거하고 실제 설정만 봅니다.
# 샘플 설정 파일cat > /tmp/myapp.conf << 'EOF'# 애플리케이션 설정 파일# 작성일: 2026-04-24# 서버 설정HOST=localhostPORT=8080# 데이터베이스 설정# DB_HOST=production.db.com ← 주석 처리됨DB_HOST=localhostDB_PORT=5432DB_NAME=myapp # 인덴트된 주석DEBUG=falseLOG_LEVEL=infoEOF
# 주석(#으로 시작) 제거, 빈 줄 제거grep -E "^[^#[:space:]]" /tmp/myapp.conf
HOST=localhost
PORT=8080
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DEBUG=false
LOG_LEVEL=info
# 조금 더 유연하게: 앞에 공백이 있어도 주석이면 제거grep -v "^[[:space:]]*#" /tmp/myapp.conf | grep -v "^[[:space:]]*$"
동일한 결과가 나옵니다. 두 번의 grep을 사용했지만 의도가 더 명확합니다.
실습 3: /etc/passwd에서 특정 쉘 사용자 찾기
# bash 쉘 사용자만grep -E ":/bin/bash$" /etc/passwd
root:x:0:0:root:/root:/bin/bash
jeongps:x:1000:1000::/home/jeongps:/bin/bash
# 로그인 불가 계정 (nologin 또는 false 쉘)grep -E ":(nologin|false)$" /etc/passwd | head -5
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
# UID가 1000 이상인 일반 사용자 계정grep -E "^[^:]+:[^:]*:[0-9]{4,}:" /etc/passwd
UID 필드(세 번째)가 네 자리 이상인 줄을 찾습니다.
실습 4: 소스코드에서 함수 정의 줄 찾기
# 샘플 쉘 스크립트 생성cat > /tmp/sample_script.sh << 'EOF'#!/bin/bash# 유틸리티 함수들log_info() { echo "[INFO] $1"}log_error() { echo "[ERROR] $1" >&2}function backup_file { local src="$1" local dst="$2" cp "$src" "$dst"}function validate_input { local value="$1" [[ -n "$value" ]] || return 1}main() { log_info "시작" backup_file /etc/passwd /tmp/passwd.bak}EOF
# Bash 함수 정의 줄 찾기 (두 가지 문법)grep -E "^[a-zA-Z_][a-zA-Z0-9_]*\(\)" /tmp/sample_script.sh
log_info() {
log_error() {
main() {
# function 키워드로 정의된 함수grep -E "^function [a-zA-Z_]" /tmp/sample_script.sh
function backup_file {
function validate_input {
# 두 형식 모두 찾기grep -E "^(function [a-zA-Z_]|[a-zA-Z_][a-zA-Z0-9_]*\(\))" /tmp/sample_script.sh
log_info() {
log_error() {
function backup_file {
function validate_input {
main() {
# 함수 이름만 추출grep -E "^(function [a-zA-Z_]|[a-zA-Z_][a-zA-Z0-9_]*\(\))" /tmp/sample_script.sh \ | grep -oE "[a-zA-Z_][a-zA-Z0-9_]*" | grep -v "^function$"
log_info
log_error
backup_file
validate_input
main
실습 5: IP 주소 형식 검증
cat > /tmp/ip_list.txt << 'EOF'192.168.1.110.0.0.5256.1.1.1192.168.11.2.3.4.5172.16.0.100abc.def.ghi.jkl0.0.0.0255.255.255.255EOF
# 형식이 맞는 IP 주소 (각 옥텟 1~3자리 숫자)grep -E "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" /tmp/ip_list.txt
192.168.1.1
10.0.0.5
256.1.1.1
172.16.0.100
0.0.0.0
255.255.255.255
256.1.1.1은 형식은 맞지만 유효 범위를 벗어납니다. 완전한 유효성 검사는 스크립트 로직이 필요합니다.
# 스크립트로 유효 범위까지 검증validate_ip() { local ip="$1" # 형식 먼저 확인 if ! echo "$ip" | grep -qE "^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$"; then return 1 fi # 각 옥텟 값 확인 local IFS='.' local octets=($ip) for octet in "${octets[@]}"; do if (( octet > 255 )); then return 1 fi done return 0}while read -r ip; do if validate_ip "$ip"; then echo "유효: $ip" else echo "무효: $ip" fidone < /tmp/ip_list.txt
유효: 192.168.1.1
유효: 10.0.0.5
무효: 256.1.1.1
무효: 192.168.1
무효: 1.2.3.4.5
유효: 172.16.0.100
무효: abc.def.ghi.jkl
유효: 0.0.0.0
유효: 255.255.255.255
정규표현식으로 형식을 걸러내고, 스크립트 로직으로 값 범위를 검증했습니다.
성능 팁
대용량 파일을 처리할 때 grep의 성능을 높이는 방법입니다.
| 팁 | 방법 | 설명 |
|---|---|---|
| 고정 문자열 | grep -F "패턴" |
정규표현식 해석 없이 빠른 검색 |
| fgrep | fgrep "패턴" |
grep -F와 동일 |
| 로케일 설정 | LC_ALL=C grep |
멀티바이트 처리 생략, 속도 향상 |
| 컬러 비활성화 | grep --color=never |
터미널 컬러 코드 생략 |
# 정규표현식이 필요 없는 단순 검색은 -F가 빠름grep -F "HTTP/1.1" /tmp/apache.log | wc -l# 대용량 로그 처리 시 로케일 설정으로 속도 향상LC_ALL=C grep "500" /tmp/apache.log# 파이프라인에서 불필요한 컬러 코드 방지grep --color=never "error" /tmp/apache.log | wc -l
PART 06의 두 장을 마쳤습니다. grep부터 정규표현식까지, 텍스트를 다루는 기본 도구들을 익혔습니다. 다음 PART에서는 이 도구들보다 훨씬 강력한 sed와 awk를 배웁니다. sed는 텍스트를 줄 단위로 변환하고, awk는 필드 단위로 처리합니다.