가이드

grep, sed, awk 예제: 리눅스 터미널에서 텍스트 처리하기

업데이트: · Skillaria.Top

grep은 패턴에 맞는 줄을 찾고(grep -rn error /var/log), sed는 스트림이나 파일의 텍스트를 바꾸며(sed -i 's/old/new/g' file), awk는 열 단위로 값을 꺼내고 계산합니다(awk '{print $1}' access.log). 세 도구를 파이프로 연결하면 짧은 명령 한 줄로 로그 분석과 설정 수정을 끝낼 수 있습니다.

grep, sed, awk는 리눅스에서 텍스트를 다루는 고전적인 도구 세 가지입니다. grep은 패턴에 맞는 줄을 찾고, sed는 에디터를 열지 않고 스트림 속 텍스트를 바꾸거나 지우며, awk는 줄을 열 단위로 나누고 계산까지 합니다. 아래에서는 로그, 설정 파일, 명령어 출력을 다룰 때 실제로 자주 쓰는 grep sed awk 예제와 흔한 실수를 정리했습니다.

언제 어떤 도구를 쓸까

세 도구 모두 텍스트를 한 줄씩 읽고 결과를 표준 출력에 쓰기 때문에 |로 쉽게 연결할 수 있습니다. 차이는 각자가 가장 잘 푸는 작업에 있습니다.

작업도구예시
문자열이 들어간 줄 찾기grepgrep ERROR app.log
텍스트 바꾸기sedsed 's/http:/https:/g' file
줄 번호로 지우거나 출력하기sedsed -n '10,20p' file
열 값 꺼내기awkawk '{print $1}' access.log
합계, 평균, 그룹별 집계awkawk '{s+=$10} END {print s}'

간단한 기준은 이렇습니다. 줄을 거르기만 하면 grep, 텍스트를 바꾸면 sed, 텍스트에 열이 있거나 계산이 필요하면 awk를 쓰십시오.

grep: 패턴으로 줄 찾기

grep 기본 예제

# 파일에서 error라는 단어가 들어간 줄
grep error /var/log/syslog

# 대소문자 무시: Error, ERROR, error 모두 찾기
grep -i error app.log

# 줄 번호와 함께 출력
grep -n "Connection refused" app.log

# 디렉터리 안을 재귀적으로 검색하고 파일 이름 표시
grep -rn "DB_HOST" /etc/myapp/

# 일치하는 파일 이름만 출력
grep -rl "TODO" src/

# 반대로: 주석과 빈 줄을 제외한 나머지 출력
grep -v -e '^#' -e '^$' /etc/ssh/sshd_config

# 일치하는 줄 수 세기
grep -c " 500 " access.log

-w 옵션은 단어 단위로 찾습니다. 그래서 grep -w log는 login을 찾지 않습니다. -F 옵션은 정규표현식을 끄고 문자열 그대로 찾습니다. 패턴에 점, 괄호, 별표가 들어 있을 때 편리합니다.

일치한 줄의 앞뒤 문맥 보기

로그의 오류는 한 줄만 보고 이해하기 어렵습니다. 오류를 일으킨 스택이나 요청까지 보려면 문맥 옵션을 쓰십시오.

# 일치한 줄 뒤 3줄 (After)
grep -A 3 "Traceback" app.log

# 일치한 줄 앞 2줄 (Before)
grep -B 2 "OOM" /var/log/kern.log

# 앞뒤 각각 5줄 (Context)
grep -C 5 "segfault" /var/log/syslog

서로 떨어진 일치 구간은 -- 줄로 구분되기 때문에 결과를 눈으로 읽기 쉽습니다.

grep과 정규표현식

grep은 기본적으로 기본 정규표현식(BRE)을 사용합니다. -E 옵션을 주면 확장 정규표현식(ERE)이 켜지고, +, ?, |, 괄호를 백슬래시 없이 쓸 수 있습니다.

표현의미예시
^줄의 시작grep '^root' /etc/passwd
$줄의 끝grep 'bash$' /etc/passwd
.임의의 문자 한 개grep 'h.t'
*0번 이상 반복grep 'ab*c'
+ (ERE)1번 이상 반복grep -E '[0-9]+'
| (ERE)또는grep -E 'error|warn'
[...]괄호 안의 문자 중 하나grep '[Ee]rror'
# 오류 또는 경고가 있는 줄
grep -E "ERROR|WARN" app.log

# 로그에서 IP 주소만 한 줄에 하나씩 출력 (-o는 일치한 부분만 출력)
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log

grep -P: 알려진 부분 사이의 값만 꺼내기

GNU grep에는 펄 스타일 정규표현식을 쓰는 -P 옵션이 있습니다. \K(앞에서 일치한 부분을 결과에서 제외)와 전방 탐색을 사용하면 값만 깔끔하게 뽑을 수 있습니다.

$ echo 'user_id=4821 status=ok' | grep -oP 'user_id=\K[0-9]+'
4821

$ grep -oP '"version":\s*"\K[^"]+' package.json
2.4.1

-P 옵션은 모든 grep에 있지는 않습니다. 예를 들어 macOS의 BSD grep에는 없습니다. 여러 시스템에서 돌아가야 하는 스크립트라면 sed나 awk를 쓰는 편이 안전합니다.

스크립트 조건문에서 grep -q

grep -q는 아무것도 출력하지 않고 종료 코드만 돌려줍니다. 일치하면 0, 없으면 1입니다. 그래서 if문에 바로 쓸 수 있습니다.

if grep -q "^PermitRootLogin no" /etc/ssh/sshd_config; then
  echo "root 로그인이 비활성화되어 있습니다"
fi

결과를 변수에 저장한 뒤 빈 값인지 확인하는 것보다 짧고, grep은 첫 번째 일치에서 바로 멈춥니다.

sed: 텍스트 치환과 삭제

치환 명령 s

sed의 핵심 명령은 s/패턴/바꿀 내용/옵션입니다. g 옵션이 없으면 줄마다 첫 번째 일치만 바뀝니다.

# 바뀐 결과만 출력 (파일은 그대로)
sed 's/foo/bar/' file.txt

# 줄 안의 모든 일치를 바꾸기
sed 's/foo/bar/g' file.txt

# 대소문자 무시 (GNU sed)
sed 's/error/ERROR/gI' app.log

# 텍스트에 슬래시가 많으면 다른 구분자 사용
sed 's|/var/www|/srv/www|g' nginx.conf

sed -i: 파일 안에서 바로 바꾸기

-i 옵션은 결과를 원본 파일에 다시 씁니다. 원본을 잃지 않으려면 백업 접미사를 붙이십시오.

# config.ini.bak 백업을 만들고 config.ini를 수정
sed -i.bak 's/^debug=true/debug=false/' config.ini

# 디렉터리 안의 모든 .conf 파일에서 치환
find /etc/myapp -name "*.conf" -exec sed -i 's/old-host/new-host/g' {} +

대량 수정 전에는 -i 없이 먼저 실행해서 결과를 확인하십시오. macOS(BSD sed)에서는 -i에 반드시 인자가 필요합니다: sed -i '' 's/a/b/' file.

줄 번호와 패턴으로 줄 다루기

# 10~20번째 줄 출력 (-n은 자동 출력을 끄고, p는 출력)
sed -n '10,20p' file.txt

# 5번째 줄 삭제
sed '5d' file.txt

# 마지막 줄 삭제
sed '$d' file.txt

# 빈 줄과 주석 삭제
sed -e '/^$/d' -e '/^#/d' nginx.conf

# "listen"이 있는 줄에서만 80을 8080으로 바꾸기
sed '/listen/s/80/8080/' nginx.conf

# 패턴과 일치하는 줄 뒤에 새 줄 추가 (GNU sed)
sed '/^\[mysqld\]/a max_connections = 500' my.cnf

역참조와 &

패턴의 괄호 부분은 \1, \2로 바꿀 내용에 다시 넣을 수 있습니다. 기호 &는 일치한 전체 문자열을 뜻합니다.

# 이름과 성의 순서 바꾸기 (-E는 확장 정규표현식)
$ echo "Kim Minsu" | sed -E 's/(\S+) (\S+)/\2 \1/'
Minsu Kim

# 모든 숫자를 대괄호로 감싸기
$ echo "port 8080, timeout 30" | sed -E 's/[0-9]+/[&]/g'
port [8080], timeout [30]

# 2026-10-14 형식의 날짜를 14.10.2026으로 바꾸기
$ echo 2026-10-14 | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
14.10.2026

awk: 열, 조건, 계산

원하는 열만 출력하기

awk는 각 줄을 공백이나 탭 기준으로 필드(열)로 나눕니다. $1은 첫 번째 필드, $2는 두 번째, $NF는 마지막 필드, $0은 줄 전체입니다.

# nginx 로그에서 IP 주소(첫 번째 열)만 출력
awk '{print $1}' /var/log/nginx/access.log

# /etc/passwd에서 사용자 이름과 셸 출력 (구분자는 콜론)
awk -F: '{print $1, $7}' /etc/passwd

# 마지막 열 출력
df -h | awk '{print $NF}'

# 출력할 때 사용할 구분자 지정
awk -F: -v OFS=';' '{print $1, $3}' /etc/passwd

조건: 필요한 줄만 출력하기

중괄호 앞에 조건을 쓰면 awk는 조건에 맞는 줄에만 동작을 수행합니다.

# UID가 1000 이상인 일반 사용자 (시스템 계정 제외)
awk -F: '$3 >= 1000 {print $1}' /etc/passwd

# 응답 코드가 500 이상인 요청 (combined 형식의 9번째 필드)
awk '$9 >= 500' access.log

# 여섯 번째 열이 정규표현식과 일치하는 줄
awk '$6 ~ /POST/' access.log

# ls -l 출력에서 100MB보다 큰 파일 이름
ls -l | awk '$5 > 100*1024*1024 {print $9}'

BEGIN, END, NR: awk를 작은 프로그램처럼

BEGIN 블록은 첫 줄을 읽기 전에, END 블록은 마지막 줄을 읽은 뒤에 실행됩니다. NR은 현재 줄 번호이고, END 블록에서는 전체 줄 수를 뜻합니다.

# 응답 크기(10번째 필드)의 합계를 메가바이트로 출력
awk '{sum += $10} END {printf "%.1f MB\n", sum/1024/1024}' access.log

# 마지막 열(응답 시간)의 평균
awk '{s += $NF} END {if (NR) print s/NR}' timings.log

# CSV 헤더 건너뛰고 두 번째 열 출력
awk -F, 'NR > 1 {print $2}' users.csv

# IP별 요청 수 세기: 연관 배열 사용
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head

마지막 예제는 로그 분석의 대표적인 패턴입니다. IP를 키로 하는 배열 count가 sort | uniq -c 조합을 대신하며, IP별 트래픽 합계 같은 계산으로도 쉽게 확장할 수 있습니다.

파이프라인: grep, sed, awk 함께 쓰기

진짜 힘은 세 도구를 조합할 때 나옵니다. 각 도구가 자기 역할을 맡고, |가 결과를 다음 단계로 넘깁니다.

5xx 오류를 가장 많이 낸 IP 상위 10개

grep ' 50[0-9] ' /var/log/nginx/access.log \
  | awk '{print $1}' \
  | sort | uniq -c | sort -rn | head -10
    412 203.0.113.45
    118 198.51.100.7
     36 192.0.2.19

여기서 grep은 필요한 줄을 대략 걸러내고, awk는 필드를 잘라내며, sort | uniq -c가 반복 횟수를 셉니다. 같은 일을 awk 한 번으로도 할 수 있습니다: awk '$9 ~ /^5/ {c[$1]++} END {for (i in c) print c[i], i}' access.log | sort -rn | head. 이쪽이 더 정확한데, 응답 코드 열을 직접 검사하기 때문입니다.

가장 많이 열린 URL

awk '{print $7}' access.log | sed 's/?.*//' | sort | uniq -c | sort -rn | head

sed는 쿼리 문자열을 잘라냅니다. 그래서 /search?q=a와 /search?q=b가 같은 페이지로 집계됩니다.

주석을 뺀 설정 파일의 키 목록

grep -v '^\s*#' /etc/myapp/app.env | grep '=' | awk -F= '{print $1}'

특정 시간대의 오류

grep '14/Oct/2026:03:' access.log | awk '$9 >= 500 {print $4, $7, $9}'

흔한 실수

  • awk 프로그램을 큰따옴표로 감싸기. awk "{print $1}"에서는 셸이 awk가 실행되기 전에 $1을 자기 변수로 바꿔 버립니다. awk 프로그램과 sed 표현식은 작은따옴표로 감싸십시오.
  • sed에서 g 잊기. g가 없으면 줄마다 첫 번째 일치만 바뀌고 나머지는 그대로 남습니다.
  • 확인 없이 sed -i 실행. 잘못된 정규표현식은 파일을 조용히 망가뜨립니다. -i 없이 먼저 실행하거나 -i.bak을 사용하십시오.
  • grep에서 +와 |가 동작하지 않음. 이 기호들은 확장 문법이므로 grep -E(또는 sed -E)가 필요합니다.
  • 패턴 속의 점. grep 192.168.1.1은 192x168y1z1도 찾습니다. 점은 임의의 문자를 뜻하기 때문입니다. grep -F를 쓰거나 \.로 이스케이프하십시오.
  • 열 구분자 미지정. 필드에 공백이 들어갈 수 있다면(CSV, 경로 등) awk -F,나 awk -F'\t'처럼 구분자를 명시하십시오.
  • 쓸데없는 cat. cat file | grep x도 동작하지만 grep x file이 더 짧고 프로세스도 하나 적습니다.

grep, sed, awk를 대신할 도구

JSON은 이 도구들로 다루기 불편합니다. 구조를 다룰 때는 jq(jq '.items[] | select(.status=="active") | .name')를, YAML에는 yq를 쓰는 편이 낫습니다. 큰 프로젝트에서 빠르게 재귀 검색을 하려면 기본적으로 .gitignore를 존중하는 ripgrep(명령어 rg)을 자주 씁니다. 펄 문법이 익숙하다면 perl -pe 's/a/b/g'와 perl -lane 'print $F[0]'이 sed와 awk를 한 줄로 대신합니다. 그래도 grep, sed, awk는 거의 모든 서버에 별도 설치 없이 들어 있으므로 가장 먼저 익혀 두실 가치가 있습니다.

정리

grep은 줄을 찾고, sed는 텍스트를 바꾸며, awk는 열을 다루고 계산합니다. 기본 조합만 기억하셔도 충분합니다. grep -rniE와 -A/-B/-C, sed 's/.../.../g'와 -i.bak, awk -F와 $1, $NF, 조건식, END 블록입니다. 이 기본기만 있으면 대부분의 로그 분석을 직접 처리할 수 있습니다. 이 grep sed awk 예제들은 find, jq, curl과 함께 강의에서 터미널 실습으로 다루고 있습니다.

자주 묻는 질문

grep과 egrep은 어떻게 다른가요?

egrep은 grep -E(확장 정규표현식)의 오래된 별칭입니다. 최신 GNU grep은 egrep 호출 시 경고를 출력하므로 grep -E를 쓰십시오.

디렉터리 안의 모든 파일에서 문자열을 찾으려면 어떻게 하나요?

grep -rn "문자열" /경로를 쓰십시오. -r은 재귀 검색, -n은 줄 번호 표시입니다. 파일 종류를 제한하려면 --include="*.php"를 추가하십시오.

여러 파일의 텍스트를 한 번에 바꾸려면 어떻게 하나요?

검색과 sed를 연결하십시오: grep -rl "old" . | xargs sed -i 's/old/new/g'. 실행 전에 커밋이나 백업을 만들어 두는 것이 좋습니다.

awk에서 여러 열을 탭으로 구분해 출력하려면 어떻게 하나요?

출력 구분자를 지정하십시오: awk -v OFS='\t' '{print $1, $3}' file. OFS를 지정하지 않으면 출력 필드는 공백으로 구분됩니다.

sed로 줄 끝의 공백을 지우려면 어떻게 하나요?

sed -i 's/[[:space:]]*$//' file 명령이 파일 모든 줄의 끝에 있는 공백과 탭을 지웁니다.

영상으로 이 주제 배우기

이 주제는 “Linux: с нуля до уверенного администрирования” 강의에서 실습과 함께 자세히 다룹니다.

강의로 이동