리눅스 grep 명령어 사용법 - 옵션 정리와 로그·코드 검색 실전 예제
이 명령어를 언제 쓰는가
리눅스에서 무언가를 찾는 일의 절반은 grep 으로 끝난다. 로그 수백만 줄에서 에러만 뽑아내고, 소스 트리에서 어떤 함수가 어디서 호출되는지 찾고, 설정 파일에서 주석을 걷어내고 실제 설정만 보는 데 쓴다.
장애 대응 중이라면 보통 이런 순서다. grep ERROR 로 에러가 있는지 확인하고, -c 로 몇 건인지 세고, -B 5 -A 20 으로 그 앞뒤 문맥을 본다. 이 세 단계를 손에 익혀 두면 대부분의 로그 분석이 된다.
기본 형식
grep [옵션] '패턴' 파일...
패턴은 정규식으로 해석된다. 셸이 * 나 $ 를 먼저 건드리지 않도록 패턴은 항상 작은따옴표로 감싸는 습관을 들인다. 파일을 생략하면 표준 입력을 읽으므로 파이프 뒤에 붙일 수 있다.
종료 코드도 중요하다. 한 건이라도 찾으면 0, 하나도 못 찾으면 1, 오류면 2다. 스크립트 조건문에서 이 값을 쓴다.
자주 쓰는 옵션
| 옵션 | 의미 | 예시 |
|---|---|---|
-i | 대소문자를 무시한다 | grep -i error app.log |
-v | 패턴에 맞지 않는 줄만 출력한다 | grep -v '^#' nginx.conf |
-n | 행 번호를 함께 출력한다 | grep -n 'TODO' main.go |
-r | 디렉터리를 재귀적으로 뒤진다 | grep -rn 'apiKey' src/ |
-l | 내용 대신 파일명만 출력한다 | grep -rl 'deprecated' src/ |
-c | 매칭된 줄 수만 센다 | grep -c 'ERROR' app.log |
-w | 단어 단위로만 매칭한다 | grep -w 'id' schema.sql |
-E | 확장 정규식을 쓴다 (|, +, ()) | grep -E '40[0-9]|50[0-9]' access.log |
-F | 패턴을 정규식이 아닌 고정 문자열로 본다 | grep -F '10.0.1.5' access.log |
-o | 매칭된 부분만 출력한다 | grep -oE '[0-9.]+' ip.txt |
-A N / -B N / -C N | 매칭 줄의 뒤 / 앞 / 양쪽 N줄을 함께 보여준다 | grep -C 3 'Traceback' app.log |
--include / --exclude-dir | 재귀 검색 대상 파일·디렉터리를 제한한다 | grep -rn --include='*.py' 'os.environ' . |
실전 예제
1) 로그에서 에러가 몇 건인지부터 센다. 장애 규모를 파악하는 첫 명령이다.
grep -c 'ERROR' /var/log/app/app.log
2) 에러 줄과 그 앞뒤 문맥을 함께 본다. 스택 트레이스는 에러 줄 하나만 봐서는 원인을 알 수 없다.
grep -n -B 3 -A 20 'ERROR' /var/log/app/app.log | less
3) 설정 파일에서 주석과 빈 줄을 걷어내고 실제 설정만 본다. 수백 줄짜리 기본 설정 파일이 열 줄로 줄어든다.
grep -vE '^[[:space:]]*#|^[[:space:]]*$' /etc/nginx/nginx.conf
\s 로 줄여 쓴 예제를 자주 보는데 그것은 GNU grep 확장이다. macOS 나 busybox 환경에서도 돌아야 하는 스크립트라면 위처럼 POSIX 문자 클래스를 쓴다.
4) 소스 트리에서 특정 키워드를 쓰는 파일을 찾는다. 빌드 산출물과 의존성 디렉터리는 반드시 제외한다.
grep -rn --include='*.js' --exclude-dir=node_modules 'process.env' .
출력 형식은 경로:행번호:내용 이다.
./src/config.js:12:const dbUrl = process.env.DATABASE_URL;
./src/server.js:5:const port = process.env.PORT || 3000;
5) 접근 로그에서 5xx 응답을 낸 요청만 뽑는다. 응답 코드 필드 위치를 따지지 않고 대략 보는 용도다.
grep -E ' 5[0-9]{2} ' /var/log/nginx/access.log | tail -n 50
6) 로그에서 IP만 추출해 빈도를 센다. -o 로 매칭 부분만 뽑아 집계 파이프라인에 넘기는 패턴이다.
grep -oE '^[0-9]{1,3}(\.[0-9]{1,3}){3}' access.log | sort | uniq -c | sort -rn | head
7) 프로세스가 떠 있는지 확인한다. grep 자기 자신이 잡히는 문제를 피하는 관용구다.
ps aux | grep -v grep | grep nginx
사실 이 목적이라면 pgrep -a nginx 가 더 정확하다.
함정과 주의점
ps aux | grep 이름 은 항상 grep 자기 자신이 함께 잡힌다. grep 프로세스의 명령줄에 검색어가 들어 있기 때문이다. 프로세스가 죽었는데도 결과가 한 줄 나와서 "살아 있네" 하고 넘어가는 사고가 실제로 난다. grep -v grep 을 앞에 붙이거나 pgrep 을 쓴다.
못 찾으면 종료 코드가 1이라 set -e 스크립트가 그 자리에서 죽는다. "에러가 없는 정상 상황"에서 배포 스크립트가 중단되는 황당한 장애의 원인이다. 결과 유무만 보려면 이렇게 감싼다.
if grep -q 'ERROR' app.log; then
echo "에러 발견"
fi
또는 grep 'ERROR' app.log || true 로 종료 코드를 무시한다.
검색어에 정규식 메타문자가 들어 있으면 엉뚱한 결과가 나온다. IP 10.0.1.5 를 그대로 검색하면 . 이 "아무 문자 하나"라서 10a0b1c5 도 잡는다. 버전 문자열, 경로, IP처럼 점이나 괄호가 든 문자열은 grep -F 로 찾는다.
grep -r 로 프로젝트 루트를 뒤지면 node_modules, .git, vendor 까지 다 읽는다. 수십 초씩 걸리고 결과도 쓰레기로 가득 찬다. --exclude-dir 을 습관적으로 붙이거나 ripgrep(rg) 을 쓴다. rg 는 .gitignore 를 알아서 존중한다.
macOS 의 grep 은 BSD 계열이라 일부 GNU 확장이 빠져 있다. -P(펄 정규식) 가 대표적이고, 정규식 안의 \s·\d 같은 축약도 안 통한다. 반대로 -A/-B/-C, --include, --exclude-dir, --line-buffered 는 macOS 의 grep 에도 있으니 "GNU 옵션은 전부 안 된다"고 오해하지 않는다. 맥에서 개발하고 리눅스 서버에서 돌리는 스크립트라면 -E 범위 안에서만 쓰는 것이 안전하다.
함께 보면 좋은 명령어
- rg (ripgrep) — 소스 코드 검색에서는 훨씬 빠르고 무시 규칙도 알아서 지킨다.
- sed — 찾은 다음 바꿔야 한다면 grep 이 아니라 sed 로 넘어간다.
- awk — 로그의 특정 컬럼만 다루거나 조건 계산이 필요할 때 grep 보다 적합하다.