이미지 로딩 중...
AI Generated
2025. 11. 18. · 3 Views
간단한 로그 검색 스크립트 완벽 가이드
실무에서 바로 사용 가능한 로그 검색 스크립트 작성법을 배웁니다. grep, 날짜/시간 필터링, 여러 조건 조합, 결과 저장, 대소문자 무시 검색, 재사용 가능한 함수까지 실전 예제와 함께 익혀보세요.
목차
1. grep으로 로그 검색하기
시작하며
여러분이 서버에서 에러가 발생했다는 알림을 받았을 때 이런 상황을 겪어본 적 있나요? 수천, 수만 줄의 로그 파일을 일일이 열어서 눈으로 찾아야 하는 상황 말이죠.
마치 도서관에서 특정 문장이 적힌 책을 찾기 위해 모든 책을 한 페이지씩 넘겨보는 것과 같습니다. 이런 문제는 실제 개발 현장에서 매일 발생합니다.
로그 파일은 시간이 지날수록 커지고, 필요한 정보는 그 속 어딘가에 숨어있습니다. 시간이 촉박한 상황에서 수동으로 찾다 보면 정작 중요한 문제 해결은 뒤로 미뤄지게 됩니다.
바로 이럴 때 필요한 것이 grep 명령어입니다. grep은 텍스트 파일에서 원하는 문자열을 빠르게 찾아주는 도구로, 로그 분석의 시작점이자 가장 강력한 무기입니다.
개요
간단히 말해서, grep은 파일 안에서 특정 단어나 패턴을 찾아주는 검색 도구입니다. 여러분이 웹 브라우저에서 Ctrl+F를 누르는 것과 비슷하지만, 훨씬 더 강력하고 빠릅니다.
왜 이 도구가 필요한지 실무 관점에서 설명하자면, 서버 로그에서 "ERROR"라는 단어가 포함된 줄만 찾아내거나, 특정 IP 주소의 접속 기록을 추적하거나, 특정 사용자의 행동을 분석할 때 매우 유용합니다. 예를 들어, 새벽 2시에 발생한 500 에러의 원인을 찾아야 할 때, grep 하나면 몇 초 만에 해당 에러 로그를 모두 찾아낼 수 있습니다.
전통적인 방법과의 비교를 해볼까요? 기존에는 텍스트 에디터로 파일을 열어서 하나하나 찾거나, 직접 스크롤하며 눈으로 확인했다면, 이제는 한 줄의 명령어로 필요한 내용만 즉시 추출할 수 있습니다.
grep의 핵심 특징은 첫째, 엄청나게 빠른 검색 속도입니다. 기가바이트 단위의 파일도 몇 초 안에 검색합니다.
둘째, 정규표현식을 지원하여 복잡한 패턴도 찾을 수 있습니다. 셋째, 다양한 옵션으로 검색 결과를 원하는 대로 가공할 수 있습니다.
이러한 특징들이 로그 분석을 수작업에서 자동화된 프로세스로 바꿔주는 이유입니다.
코드 예제
# 기본 grep 사용법 - ERROR가 포함된 줄 찾기
grep "ERROR" /var/log/app.log
# 줄 번호와 함께 출력
grep -n "ERROR" /var/log/app.log
# 매칭된 부분을 색깔로 강조
grep --color "ERROR" /var/log/app.log
# 매칭된 줄의 앞뒤 2줄씩 함께 출력 (컨텍스트 확인)
grep -C 2 "ERROR" /var/log/app.log
# 여러 파일에서 동시에 검색
grep "ERROR" /var/log/*.log
설명
이것이 하는 일: grep 명령어는 파일의 모든 줄을 하나씩 읽으면서 여러분이 지정한 패턴과 일치하는 줄을 찾아서 화면에 출력합니다. 마치 탐정이 증거를 찾듯이, 파일 전체를 스캔하면서 조건에 맞는 줄만 골라냅니다.
첫 번째로, grep "ERROR" /var/log/app.log 명령은 가장 기본적인 형태입니다. 이 명령은 app.log 파일을 열어서 "ERROR"라는 문자열이 포함된 모든 줄을 찾아 출력합니다.
왜 이렇게 하는지 궁금하시죠? 에러 로그만 모아서 보면 문제가 무엇인지 한눈에 파악할 수 있기 때문입니다.
그 다음으로, -n 옵션을 추가하면 줄 번호가 함께 표시됩니다. 예를 들어 "127:ERROR: Database connection failed"처럼 출력되는데, 이렇게 하면 원본 파일의 정확한 위치를 알 수 있어 나중에 해당 부분을 다시 확인하기 쉽습니다.
--color 옵션은 매칭된 "ERROR" 부분을 빨간색으로 강조해서 보여주므로 시각적으로 훨씬 찾기 쉽습니다. 더 유용한 기능으로 -C 2 옵션이 있습니다.
이것은 매칭된 줄뿐만 아니라 그 앞뒤로 2줄씩 더 보여줍니다. 왜 이게 중요할까요?
에러 메시지만 보면 원인을 알기 어려울 때가 많은데, 앞뒤 문맥을 보면 "아, 이 사용자가 이런 요청을 보냈고, 그래서 이 에러가 발생했구나"라고 이해할 수 있기 때문입니다. 여러분이 이 명령어를 사용하면 몇 가지 구체적인 이점을 얻을 수 있습니다.
첫째, 시간 절약입니다. 10만 줄짜리 로그에서 에러 100개를 찾는 데 1초도 걸리지 않습니다.
둘째, 정확성입니다. 사람이 직접 찾으면 놓치는 경우가 많지만 grep은 절대 놓치지 않습니다.
셋째, 자동화 가능성입니다. 스크립트에 넣어두면 매일 자동으로 로그를 분석할 수 있습니다.
실전 팁
💡 grep -i를 사용하면 대소문자를 구분하지 않고 검색합니다. "error", "Error", "ERROR" 모두 찾아낼 수 있어 로그에서 누락을 방지할 수 있습니다.
💡 파일이 너무 크면 grep "ERROR" huge.log | less처럼 less와 조합하세요. 결과를 한 페이지씩 볼 수 있어 화면이 넘쳐나는 것을 방지합니다.
💡 grep -v "DEBUG"는 반대로 "DEBUG"가 포함되지 않은 줄만 보여줍니다. 디버그 메시지를 제외하고 중요한 로그만 볼 때 유용합니다.
💡 grep -c "ERROR"는 매칭된 줄의 개수만 세어줍니다. "오늘 에러가 몇 건 발생했지?"라는 질문에 빠르게 답할 수 있습니다.
💡 실시간 로그를 보려면 tail -f app.log | grep "ERROR"를 사용하세요. 새로 추가되는 로그 중 에러만 실시간으로 모니터링할 수 있습니다.
2. 특정 날짜/시간 로그 추출
시작하며
여러분이 고객으로부터 "어제 오후 3시쯤 결제가 안 됐어요"라는 문의를 받았을 때 이런 상황을 겪어본 적 있나요? 며칠치 로그가 섞여있는 파일에서 정확히 그 시간대의 로그만 찾아야 하는 상황 말이죠.
마치 CCTV 영상에서 특정 시간대만 빨리 감기로 찾는 것과 비슷합니다. 이런 문제는 실제 개발 현장에서 정말 자주 발생합니다.
로그는 계속 쌓이고, 문제는 특정 시점에 발생하며, 그 시점 전후의 상황을 파악하는 것이 문제 해결의 핵심입니다. 전체 로그를 다 보면 시간도 오래 걸리고 정작 중요한 정보를 놓치기 쉽습니다.
바로 이럴 때 필요한 것이 날짜와 시간 기반 로그 필터링입니다. grep과 정규표현식을 조합하면 원하는 시간대의 로그만 정확하게 추출할 수 있습니다.
개요
간단히 말해서, 날짜/시간 로그 추출은 로그 파일에서 특정 날짜나 시간대에 발생한 이벤트만 골라내는 기술입니다. 로그의 타임스탬프 패턴을 이용해 원하는 시간 범위를 지정합니다.
왜 이 기술이 필요한지 실무 관점에서 설명하자면, 장애가 발생한 정확한 시점의 로그를 분석해야 원인을 찾을 수 있기 때문입니다. 예를 들어, 오전 11시 23분에 서버가 다운됐다면 11시 20분부터 11시 25분 사이의 로그를 집중적으로 봐야 합니다.
전체 로그를 보는 건 건초더미에서 바늘 찾기와 같습니다. 전통적인 방법과의 비교를 해볼까요?
기존에는 로그 파일을 열어서 스크롤하며 날짜를 찾거나, 수동으로 해당 부분을 복사했다면, 이제는 정규표현식 하나로 정확히 원하는 시간대만 자동으로 추출할 수 있습니다. 이 기술의 핵심 특징은 첫째, 정확성입니다.
사람이 찾으면 실수할 수 있지만 패턴 매칭은 정확합니다. 둘째, 유연성입니다.
특정 시각, 시간 범위, 날짜 범위 등 다양한 조건으로 검색 가능합니다. 셋째, 속도입니다.
기가바이트 단위 로그에서도 몇 초 안에 원하는 시간대를 찾아냅니다. 이러한 특징들이 빠른 문제 해결을 가능하게 합니다.
코드 예제
# 2024-11-18 날짜의 로그만 추출
grep "2024-11-18" /var/log/app.log
# 특정 시간대 (14:00~14:59) 로그 추출
grep "2024-11-18 14:" /var/log/app.log
# 정규표현식으로 14:30~14:39 사이 추출
grep "2024-11-18 14:3[0-9]" /var/log/app.log
# 여러 날짜 중 하나와 매칭 (11월 15일부터 17일)
grep -E "2024-11-(15|16|17)" /var/log/app.log
# 시간 범위를 좀 더 유연하게 (오전 시간대만)
grep -E "2024-11-18 (09|10|11):" /var/log/app.log
설명
이것이 하는 일: 날짜/시간 기반 검색은 로그의 타임스탬프 형식을 파악한 후, 그 패턴과 매칭되는 특정 시간대의 로그만 필터링합니다. 마치 시간 여행 기계처럼 원하는 시점으로 정확히 이동하는 것입니다.
첫 번째로, grep "2024-11-18" 같은 기본 검색은 해당 날짜가 포함된 모든 줄을 찾습니다. 대부분의 로그는 "[2024-11-18 14:32:15]" 같은 형식으로 시작하므로, 날짜만 지정해도 그날의 모든 로그를 추출할 수 있습니다.
왜 이렇게 간단할까요? 로그 포맷이 표준화되어 있기 때문입니다.
그 다음으로, 시간까지 지정하려면 "2024-11-18 14:"처럼 시(hour)까지 포함시킵니다. 이렇게 하면 14시대 (14:00:00~14:59:59) 모든 로그가 추출됩니다.
더 정밀하게 14시 30분대만 보려면 14:3[0-9]라는 정규표현식을 사용합니다. 대괄호 안의 [0-9]는 0부터 9까지 아무 숫자나 올 수 있다는 뜻이므로, 14:30부터 14:39까지 매칭됩니다.
조금 더 복잡한 예로 -E 옵션을 사용하는 경우를 봅시다. grep -E "2024-11-(15|16|17)"는 확장 정규표현식 모드로, 괄호 안의 값 중 하나와 매칭됩니다.
즉, 11월 15일, 16일, 17일 중 하나라도 포함되면 출력됩니다. 이것은 며칠 동안 발생한 문제를 추적할 때 아주 유용합니다.
마지막으로, 시간 범위를 지정하는 (09|10|11):는 오전 9시부터 11시까지의 로그만 보여줍니다. 파이프(|)는 "또는"을 의미하므로, 9시 또는 10시 또는 11시에 해당하는 로그가 모두 출력됩니다.
이렇게 하면 "오전 업무 시간대에 무슨 일이 있었지?"라는 질문에 바로 답할 수 있습니다. 여러분이 이 기술을 사용하면 문제 해결 시간이 획기적으로 단축됩니다.
예를 들어, "11시 23분에 에러 발생"이라는 리포트를 받으면, 11시 20분부터 25분까지 5분간의 로그만 보면 됩니다. 수만 줄의 로그에서 핵심 부분만 추출하니 분석이 훨씬 쉬워지고, 원인도 빠르게 찾을 수 있습니다.
실전 팁
💡 로그의 날짜 형식을 먼저 확인하세요. head -5 app.log로 처음 5줄을 보면 "[2024-11-18]"인지 "2024/11/18"인지 알 수 있습니다. 형식에 맞춰 패턴을 작성해야 합니다.
💡 시간 범위가 복잡하면 두 번의 grep을 사용하세요. grep "2024-11-18" app.log | grep -E "(14|15|16):"처럼 파이프로 연결하면 더 읽기 쉽습니다.
💡 자정을 넘어가는 시간 범위는 날짜별로 나눠서 검색하세요. 23시~01시 로그를 보려면 "11-17 23:"과 "11-18 0[0-1]:"을 각각 검색해야 합니다.
💡 grep -A 10 옵션으로 매칭된 줄 이후 10줄을 더 보세요. 시작 시간만 지정하고 그 이후 일어난 일을 추적할 때 유용합니다.
💡 정규표현식이 복잡하면 변수에 저장하세요. PATTERN="2024-11-18 14:3[0-9]", grep "$PATTERN" app.log처럼 사용하면 재사용이 쉽습니다.
3. 여러 조건으로 필터링
시작하며
여러분이 "11월 18일 오후 2시에 특정 사용자가 결제 실패 에러를 겪었다"는 복잡한 조건으로 로그를 찾아야 할 때 이런 상황을 겪어본 적 있나요? 날짜도 맞아야 하고, 시간도 맞아야 하고, 특정 키워드도 포함되어야 하고, 특정 사용자 ID도 있어야 하는 상황 말이죠.
마치 여러 개의 필터를 동시에 적용해야 하는 정수기처럼요. 이런 문제는 실제 개발 현장에서 거의 매번 발생합니다.
실무에서는 단순히 "ERROR를 찾아줘"가 아니라 "특정 시간대에, 특정 API에서, 특정 사용자가 겪은 에러를 찾아줘"처럼 복합적인 조건이 주어집니다. 하나의 조건만으로는 결과가 너무 많아서 핵심을 찾기 어렵습니다.
바로 이럴 때 필요한 것이 다중 조건 필터링입니다. grep을 여러 번 연결하거나, 정규표현식을 조합하면 복잡한 조건도 한 번에 처리할 수 있습니다.
개요
간단히 말해서, 여러 조건 필터링은 AND(그리고)나 OR(또는) 논리를 사용해서 여러 검색 조건을 동시에 적용하는 기술입니다. 마치 쇼핑몰에서 "가격 10만원 이하, 색상 파란색, 브랜드 나이키"처럼 여러 필터를 동시에 적용하는 것과 같습니다.
왜 이 기술이 필요한지 실무 관점에서 설명하자면, 로그는 정말 방대하기 때문입니다. 예를 들어, "ERROR"만 검색하면 수천 건이 나올 수 있는데, "ERROR이면서 payment 관련이고, user_id가 12345인 것"으로 좁히면 정확히 2~3건만 나옵니다.
이렇게 조건을 추가할수록 원하는 정보에 가까워집니다. 전통적인 방법과의 비교를 해볼까요?
기존에는 첫 번째 조건으로 검색한 결과를 파일로 저장하고, 그 파일에서 두 번째 조건을 검색하고, 또 그 결과를 저장하는 식으로 여러 단계를 거쳤다면, 이제는 파이프(|)로 연결하여 한 줄로 처리할 수 있습니다. 이 기술의 핵심 특징은 첫째, 정확성입니다.
모든 조건을 만족하는 로그만 정확히 찾아냅니다. 둘째, 효율성입니다.
파이프를 사용하면 중간 결과를 파일로 저장할 필요 없이 메모리에서 바로 처리됩니다. 셋째, 가독성입니다.
각 조건을 단계별로 연결하므로 나중에 스크립트를 봐도 무슨 조건인지 쉽게 이해할 수 있습니다. 이러한 특징들이 복잡한 로그 분석을 가능하게 합니다.
코드 예제
# AND 조건: ERROR이면서 payment 포함 (두 조건 모두 만족)
grep "ERROR" /var/log/app.log | grep "payment"
# 세 가지 조건 모두 만족: 날짜 + ERROR + 특정 user_id
grep "2024-11-18" /var/log/app.log | grep "ERROR" | grep "user_id: 12345"
# OR 조건: ERROR 또는 WARN 중 하나라도 포함
grep -E "ERROR|WARN" /var/log/app.log
# 복합 조건: (ERROR 또는 FATAL)이면서 database 포함
grep -E "ERROR|FATAL" /var/log/app.log | grep "database"
# NOT 조건 추가: ERROR이지만 DEBUG는 제외
grep "ERROR" /var/log/app.log | grep -v "DEBUG"
# 복잡한 조건: 특정 시간대, ERROR, 특정 API, INFO 제외
grep "2024-11-18 14:" /var/log/app.log | grep "ERROR" | grep "/api/payment" | grep -v "INFO"
설명
이것이 하는 일: 여러 조건 필터링은 로그를 단계적으로 걸러내는 과정입니다. 첫 번째 grep이 큰 그물로 걸러내면, 두 번째 grep이 더 작은 그물로 한 번 더 거르고, 이런 식으로 계속 정제하여 최종적으로 정확한 결과만 남깁니다.
첫 번째로, 가장 기본적인 AND 조건인 grep "ERROR" | grep "payment"를 봅시다. 이것은 두 단계로 작동합니다.
먼저 "ERROR"가 포함된 모든 줄을 찾고, 그 결과를 두 번째 grep으로 넘겨서 "payment"도 포함된 줄만 남깁니다. 왜 이렇게 할까요?
한 번에 복잡한 정규표현식을 쓰는 것보다 단계별로 나누는 게 이해하기 쉽고 수정도 쉽기 때문입니다. 그 다음으로, 세 가지 조건을 연결하는 경우를 봅시다.
grep "2024-11-18" | grep "ERROR" | grep "user_id: 12345"는 파이프를 두 번 사용합니다. 첫 번째 grep이 해당 날짜의 로그를 걸러내면 수천 줄이 나올 수 있습니다.
두 번째 grep이 그중에서 ERROR만 걸러내면 수백 줄로 줄어듭니다. 마지막 grep이 특정 사용자만 걸러내면 최종적으로 2~3줄만 남습니다.
이렇게 단계적으로 좁혀가는 것이 핵심입니다. OR 조건은 조금 다릅니다.
grep -E "ERROR|WARN"는 확장 정규표현식을 사용하여 "ERROR" 또는 "WARN" 중 하나라도 포함되면 출력합니다. 파이프(|)가 "또는"을 의미합니다.
이것은 "중요한 문제는 모두 보고 싶은데, ERROR와 WARN 레벨이 중요해"라는 상황에 쓰입니다. NOT 조건은 -v 옵션으로 구현합니다.
grep "ERROR" | grep -v "DEBUG"는 ERROR를 포함하지만 DEBUG는 포함하지 않는 줄을 찾습니다. 이것은 역필터링이라고 할 수 있는데, "이건 제외하고 보고 싶어"라는 요구사항에 딱 맞습니다.
예를 들어 디버그 레벨의 에러 로그는 개발 중에만 의미 있고 실제 에러는 아니므로 제외하는 식입니다. 여러분이 이 기술을 마스터하면 어떤 복잡한 조건도 다룰 수 있습니다.
실제 예를 들어볼까요? "11월 18일 오후 2시대에, 결제 API에서, ERROR 또는 FATAL 레벨이 발생했지만, 알려진 버그(known_issue)는 제외하고 싶다"라는 요구사항이 있다면, grep "2024-11-18 14:" app.log | grep "/api/payment" | grep -E "ERROR|FATAL" | grep -v "known_issue" 한 줄로 해결됩니다.
마치 레고 블록을 조립하듯이 조건을 추가하고 조합할 수 있습니다.
실전 팁
💡 조건이 많을수록 파이프 순서가 중요합니다. 가장 많이 걸러낼 수 있는 조건을 앞에 두면 다음 단계에서 처리할 데이터가 줄어들어 속도가 빨라집니다.
💡 복잡한 OR 조건은 괄호로 묶으세요. grep -E "(ERROR|FATAL|CRITICAL).*database"처럼 쓰면 세 가지 레벨 중 하나이면서 database를 포함하는 로그를 찾습니다.
💡 여러 NOT 조건이 필요하면 정규표현식으로 한 번에 처리하세요. grep -vE "(DEBUG|TRACE|INFO)"는 세 가지를 모두 제외합니다.
💡 자주 쓰는 조건 조합은 함수로 만들어두세요. 나중에 "4. 검색 결과 파일로 저장"에서 배울 내용이지만, 미리 생각해두면 좋습니다.
💡 파이프가 너무 길어지면 백슬래시()로 여러 줄로 나누세요. 가독성이 훨씬 좋아집니다: grep "ERROR" app.log | \ (다음 줄) grep "payment".
4. 검색 결과 파일로 저장
시작하며
여러분이 복잡한 조건으로 로그를 검색해서 중요한 결과를 찾았을 때 이런 상황을 겪어본 적 있나요? "아, 이 결과를 팀원들에게 공유해야 하는데, 다시 같은 명령어를 치기는 번거롭고, 터미널 내용을 복사하기는 너무 길고..." 마치 중요한 회의 내용을 메모해두지 않아서 나중에 다시 회의해야 하는 것과 비슷합니다.
이런 문제는 실제 개발 현장에서 정말 자주 발생합니다. 로그 분석 결과는 보고서에 첨부해야 하고, 다른 팀원과 공유해야 하며, 나중에 다시 참조해야 할 수도 있습니다.
터미널 화면에만 표시되면 스크롤 제한 때문에 사라질 수도 있고, 복사하기도 불편합니다. 바로 이럴 때 필요한 것이 검색 결과를 파일로 저장하는 기술입니다.
리다이렉션(>)과 파이프를 활용하면 grep 결과를 깔끔하게 파일로 저장할 수 있습니다.
개요
간단히 말해서, 검색 결과 저장은 터미널에 출력되는 내용을 파일로 보내는 것입니다. 마치 프린터로 종이에 인쇄하듯이, 화면 대신 파일로 출력하는 것이죠.
왜 이 기술이 필요한지 실무 관점에서 설명하자면, 첫째로 재현 가능성입니다. 같은 명령어를 다시 칠 필요 없이 파일을 열면 됩니다.
예를 들어, 장애 분석 결과를 저장해두면 다음 회의에서 그 파일만 열어서 보여주면 됩니다. 둘째로 공유 용이성입니다.
파일은 메일로 보내거나 슬랙에 업로드하기 쉽습니다. 셋째로 추가 분석이 가능합니다.
저장된 파일을 Excel이나 다른 도구로 열어서 차트를 그릴 수도 있습니다. 전통적인 방법과의 비교를 해볼까요?
기존에는 터미널 내용을 마우스로 드래그해서 복사하고, 텍스트 에디터를 열어서 붙여넣고, 저장했다면, 이제는 명령어 끝에 > result.txt 몇 글자만 추가하면 자동으로 저장됩니다. 이 기술의 핵심 특징은 첫째, 간편함입니다.
> 기호 하나면 됩니다. 둘째, 추가 저장도 가능합니다.
>> 를 사용하면 기존 파일에 내용을 덧붙입니다. 셋째, 에러도 함께 저장할 수 있습니다.
2>&1을 사용하면 에러 메시지도 파일로 저장됩니다. 이러한 특징들이 로그 분석 결과를 체계적으로 관리할 수 있게 해줍니다.
코드 예제
# 기본: 검색 결과를 파일로 저장 (기존 파일 덮어쓰기)
grep "ERROR" /var/log/app.log > error_logs.txt
# 추가 모드: 기존 파일 끝에 내용 추가
grep "WARN" /var/log/app.log >> error_logs.txt
# 여러 조건 검색 결과 저장
grep "2024-11-18" /var/log/app.log | grep "ERROR" > today_errors.txt
# 에러 메시지도 함께 저장 (표준 출력 + 표준 에러)
grep "ERROR" /var/log/app.log > result.txt 2>&1
# 화면에도 표시하면서 동시에 파일로 저장 (tee 사용)
grep "ERROR" /var/log/app.log | tee error_logs.txt
# 날짜별로 파일명 자동 생성
grep "ERROR" /var/log/app.log > "errors_$(date +%Y%m%d).txt"
설명
이것이 하는 일: 리다이렉션은 터미널에 출력될 내용의 방향을 바꿔서 파일로 보냅니다. 마치 물이 흐르는 방향을 바꾸는 수로처럼, 데이터의 흐름을 화면에서 파일로 전환하는 것입니다.
첫 번째로, grep "ERROR" app.log > error_logs.txt는 가장 기본적인 형태입니다. > 기호는 "오른쪽 파일로 출력을 보내라"는 뜻입니다.
이 명령을 실행하면 ERROR가 포함된 모든 줄이 error_logs.txt 파일에 저장됩니다. 만약 파일이 이미 존재하면 기존 내용은 지워지고 새로운 내용으로 완전히 대체됩니다.
왜 기존 내용을 지울까요? >는 "새로 만들기"를 의미하기 때문입니다.
그 다음으로, >> (이중 꺾쇠)는 추가 모드입니다. grep "WARN" app.log >> error_logs.txt를 실행하면 기존 파일 끝에 내용이 덧붙여집니다.
이것은 여러 번 검색한 결과를 하나의 파일에 모을 때 유용합니다. 예를 들어, 먼저 ERROR를 저장하고, 그다음에 WARN을 추가로 저장하고, 또 FATAL을 추가로 저장하는 식으로 점진적으로 파일을 만들 수 있습니다.
아주 유용한 도구로 tee 명령이 있습니다. grep "ERROR" app.log | tee error_logs.txt는 결과를 화면에도 보여주면서 동시에 파일로도 저장합니다.
마치 T자 파이프처럼 데이터가 두 방향으로 갈라집니다. 왜 이게 유용할까요?
결과를 눈으로 확인하면서 동시에 저장할 수 있어서, "잘 저장됐나?" 걱정할 필요가 없기 때문입니다. 에러 처리도 중요합니다.
2>&1은 표준 에러(stderr)를 표준 출력(stdout)으로 합칩니다. 예를 들어 grep 명령어가 "파일을 찾을 수 없습니다"라는 에러를 출력하면, 그 에러 메시지도 파일에 저장됩니다.
이것은 나중에 "왜 결과가 없지?"라고 궁금할 때 파일을 열어보면 "아, 파일 경로가 잘못됐구나"라고 알 수 있게 해줍니다. 여러분이 날짜별로 파일을 관리하고 싶다면 $(date +%Y%m%d) 같은 명령 치환을 사용할 수 있습니다.
이것은 현재 날짜를 "20241118" 형식으로 만들어서 파일명에 넣어줍니다. 매일 자동으로 로그를 분석하는 스크립트를 만들 때, 이렇게 하면 "errors_20241118.txt", "errors_20241119.txt"처럼 자동으로 날짜별 파일이 생성됩니다.
한 달 뒤에 "11월 18일에 무슨 에러가 있었지?"라고 궁금하면 해당 파일만 열어보면 됩니다.
실전 팁
💡 실수로 중요한 파일을 덮어쓰지 않으려면 set -o noclobber를 터미널에 입력하세요. 그러면 >로 기존 파일을 덮어쓰려 할 때 에러가 납니다. 강제로 덮어쓰려면 >|를 사용하세요.
💡 큰 결과를 저장할 때는 진행 상황을 보고 싶으면 pv를 사용하세요. grep "ERROR" huge.log | pv | tee result.txt처럼 하면 진행률 바가 표시됩니다.
💡 파일 저장 위치는 절대 경로로 지정하세요. > /home/user/logs/result.txt처럼 하면 현재 디렉토리가 어디든 항상 같은 곳에 저장됩니다.
💡 결과가 비어있을 수 있으므로 저장 후 확인하세요. grep "ERROR" app.log > result.txt && echo "Saved $(wc -l < result.txt) lines"처럼 하면 몇 줄이 저장됐는지 알려줍니다.
💡 압축해서 저장하면 공간을 절약할 수 있습니다. grep "ERROR" app.log | gzip > result.txt.gz처럼 하면 파일 크기가 10분의 1로 줄어듭니다.
5. 대소문자 구분 없이 검색
시작하며
여러분이 로그에서 에러를 검색했는데 결과가 너무 적게 나와서 "이상한데?"라고 생각한 적 있나요? 나중에 확인해보니 어떤 개발자는 "error"로, 다른 개발자는 "Error"로, 또 어떤 시스템은 "ERROR"로 로그를 남겼던 상황 말이죠.
마치 같은 이름을 "김철수", "Kim Chulsoo", "KIM CHULSOO"로 다르게 쓰는 것처럼요. 이런 문제는 실제 개발 현장에서 정말 자주 발생합니다.
여러 시스템이 연동되어 있거나, 여러 개발자가 참여한 프로젝트에서는 로그 메시지의 대소문자가 일관되지 않은 경우가 많습니다. Java는 "NullPointerException", Python은 "nullpointerexception", JavaScript는 "NullPointerError"처럼 언어마다 표기법도 다릅니다.
바로 이럴 때 필요한 것이 대소문자 구분 없는 검색입니다. -i 옵션 하나로 모든 변형을 한 번에 찾을 수 있습니다.
개요
간단히 말해서, 대소문자 무시 검색은 "error", "Error", "ERROR", "eRRoR" 등 모든 대소문자 조합을 동일하게 취급하는 검색 방식입니다. 마치 검색 엔진이 "서울"과 "SEOUL"을 같은 것으로 인식하는 것과 비슷합니다.
왜 이 기술이 필요한지 실무 관점에서 설명하자면, 놓치는 로그가 없도록 하기 위해서입니다. 예를 들어, 프론트엔드 로그는 "Error:", 백엔드 로그는 "ERROR:", 데이터베이스 로그는 "error:"로 시작한다면, 세 번 검색해야 할까요?
아닙니다. -i 옵션 하나면 한 번에 모두 찾습니다.
이것은 완전성(completeness)을 보장하는 핵심 기술입니다. 전통적인 방법과의 비교를 해볼까요?
기존에는 grep -E "error|Error|ERROR"처럼 모든 변형을 일일이 나열했다면, 이제는 grep -i "error" 한 번으로 끝입니다. 3개 조건이 1개로 줄어드는 것이죠.
이 기술의 핵심 특징은 첫째, 완전성입니다. 어떤 대소문자 조합도 놓치지 않습니다.
둘째, 간결성입니다. 옵션 하나로 해결됩니다.
셋째, 성능입니다. 여러 OR 조건보다 -i 하나가 더 빠릅니다.
이러한 특징들이 실무에서 빠뜨림 없는 로그 분석을 가능하게 합니다.
코드 예제
# 대소문자 구분 없이 error 검색 (error, Error, ERROR 모두 매칭)
grep -i "error" /var/log/app.log
# 여러 조건과 함께 사용
grep -i "error" /var/log/app.log | grep -i "payment"
# 줄 번호와 색깔 강조 함께 사용
grep -in --color "error" /var/log/app.log
# 파일 이름만 출력 (여러 파일 검색 시)
grep -il "error" /var/log/*.log
# 대소문자 무시 + OR 조건
grep -iE "error|warn|fail" /var/log/app.log
# 특정 단어들을 대소문자 무시하면서 검색
grep -i "null.*exception" /var/log/app.log
설명
이것이 하는 일: -i 옵션은 grep에게 "대소문자를 신경 쓰지 말고 알파벳 자체만 비교해"라고 지시합니다. 내부적으로는 모든 문자를 소문자(또는 대문자)로 통일해서 비교하는 방식으로 작동합니다.
첫 번째로, grep -i "error" app.log는 가장 기본적인 사용법입니다. 이 명령은 "error", "Error", "ERROR", "ErRoR", "eRrOr" 등 26개 알파벳의 대소문자 조합 2^5 = 32가지를 모두 찾아냅니다.
왜 이렇게 많은 조합을 고려해야 할까요? 로그는 여러 소스에서 오기 때문에 표준화되어 있지 않기 때문입니다.
어떤 라이브러리는 "Error", 어떤 프레임워크는 "ERROR"를 사용합니다. 그 다음으로, -i와 다른 옵션을 조합할 수 있습니다.
grep -in --color "error"에서 -in은 -i와 -n을 합친 것입니다. 하나는 대소문자 무시, 다른 하나는 줄 번호 표시입니다.
옵션들은 순서에 상관없이 조합 가능하며, grep -ni와 grep -in은 완전히 동일합니다. 이렇게 하면 "127:Error: Connection failed"처럼 줄 번호와 함께 결과가 나옵니다.
파일 검색에서도 유용합니다. grep -il "error" *.log는 "error"를 포함한 파일의 이름만 출력합니다(-l 옵션).
대소문자 무시(-i)와 결합하면, 100개의 로그 파일 중에서 어떤 형태로든 "error"가 들어있는 파일만 골라냅니다. 예를 들어 "app.log", "database.log", "auth.log"만 출력되면, 나머지 97개 파일은 에러가 없다는 뜻입니다.
복잡한 패턴에도 적용됩니다. grep -i "null.*exception"은 "null"로 시작하고 나중에 "exception"이 나오는 패턴을 찾는데, 둘 다 대소문자를 무시합니다.
따라서 "NullPointerException", "null pointer exception", "NULL EXCEPTION" 등을 모두 찾습니다. 정규표현식의 .*은 "중간에 아무거나 올 수 있다"는 뜻이므로, "null이 발생한 exception"처럼 중간에 다른 단어가 있어도 매칭됩니다.
여러분이 이 옵션을 사용하면 놓치는 로그를 크게 줄일 수 있습니다. 실제 사례를 들어볼까요?
어떤 회사에서 "대소문자 구분 검색으로 에러를 찾았더니 10건이 나왔는데, -i 옵션을 추가하니 37건이 나왔다"는 경우가 있었습니다. 27건을 놓칠 뻔한 것이죠.
특히 여러 팀이 협업하거나 오픈소스 라이브러리를 사용하는 환경에서는 -i가 거의 필수입니다.
실전 팁
💡 변수명이나 함수명을 찾을 때는 -i를 사용하지 마세요. 코드에서는 getUserName과 getusername이 다른 함수이므로 대소문자를 정확히 구분해야 합니다.
💡 정확한 매칭이 필요한 경우 -w 옵션을 함께 쓰세요. grep -iw "error"는 "error"라는 단어만 찾고 "errors"나 "terrorize"는 찾지 않습니다.
💡 SQL 쿼리 로그를 찾을 때 유용합니다. grep -i "select.*from.*users"는 "SELECT", "select", "Select" 모두를 찾아 SQL 쿼리를 빠짐없이 추적합니다.
💡 HTTP 메서드를 찾을 때도 활용하세요. grep -i "method: get"은 "GET", "get", "Get" 모두를 찾아 API 호출을 분석할 수 있습니다.
💡 환경 변수 이름은 보통 대문자이지만 설정 파일에 따라 다를 수 있으므로, grep -i "database_url" 같이 검색하면 안전합니다.
6. 재사용 가능한 검색 함수
시작하며
여러분이 매일 아침 출근해서 같은 로그 검색 명령어를 반복해서 치고 있는 자신을 발견한 적 있나요? "어제도 쳤던 건데, 오늘도 똑같이 치네.
내일도 또 쳐야 하나?" 하는 생각이 드는 상황 말이죠. 마치 매번 커피를 내리는 복잡한 과정을 반복하는 것처럼, 버튼 하나로 해결할 수 있으면 좋을 텐데요.
이런 문제는 실제 개발 현장에서 정말 흔합니다. 로그 분석은 일회성이 아니라 반복 작업입니다.
매일 같은 패턴의 에러를 찾고, 같은 형식으로 저장하고, 같은 조건으로 필터링합니다. 명령어가 길고 복잡할수록 매번 치기 번거롭고, 오타가 날 확률도 높아집니다.
바로 이럴 때 필요한 것이 재사용 가능한 검색 함수입니다. Bash 함수로 만들어두면 복잡한 명령어를 간단한 이름으로 호출할 수 있습니다.
개요
간단히 말해서, Bash 함수는 여러 명령어를 하나로 묶어서 이름을 붙이는 것입니다. 마치 자주 가는 장소를 즐겨찾기에 저장해두는 것처럼, 자주 쓰는 명령어를 함수 이름으로 저장해두는 것이죠.
왜 이 기술이 필요한지 실무 관점에서 설명하자면, 첫째로 효율성입니다. grep "2024-11-18" app.log | grep -i "error" | grep "payment" > result.txt 같은 긴 명령어를 search_payment_errors 2024-11-18처럼 짧게 줄일 수 있습니다.
둘째로 일관성입니다. 팀원 모두가 같은 함수를 사용하면 검색 방식이 표준화됩니다.
셋째로 유지보수입니다. 검색 조건을 바꿔야 할 때 함수 하나만 수정하면 모든 곳에 적용됩니다.
전통적인 방법과의 비교를 해볼까요? 기존에는 명령어를 메모장에 복사해두고 필요할 때마다 붙여넣었다면, 이제는 함수를 ~/.bashrc에 저장해두고 언제든 호출할 수 있습니다.
붙여넣기는 매번 메모장을 열어야 하지만, 함수는 그냥 타이핑하면 됩니다. 이 기술의 핵심 특징은 첫째, 매개변수를 받을 수 있다는 점입니다.
날짜, 키워드, 파일명 등을 변수로 받아 유연하게 사용합니다. 둘째, 재사용성입니다.
한 번 만들어두면 영원히 쓸 수 있습니다. 셋째, 공유 가능성입니다.
좋은 함수는 팀 전체가 사용할 수 있도록 공유할 수 있습니다. 이러한 특징들이 로그 분석 작업을 자동화하고 표준화하는 기반이 됩니다.
코드 예제
# 기본 함수: 특정 날짜의 에러 로그 검색
search_errors() {
local date=$1 # 첫 번째 인자를 날짜로 받음
grep "$date" /var/log/app.log | grep -i "error" | tee "errors_$date.txt"
echo "Results saved to errors_$date.txt"
}
# 사용법: search_errors 2024-11-18
# 고급 함수: 여러 조건을 매개변수로 받기
search_logs() {
local date=$1
local level=$2
local keyword=$3
local output_file="logs_${date}_${level}.txt"
grep "$date" /var/log/app.log | \
grep -i "$level" | \
grep -i "$keyword" | \
tee "$output_file"
echo "Found $(wc -l < "$output_file") matching lines"
}
# 사용법: search_logs 2024-11-18 ERROR payment
# 실용 함수: 최근 N분간의 에러 로그
recent_errors() {
local minutes=${1:-10} # 기본값 10분
local since=$(date -d "$minutes minutes ago" "+%Y-%m-%d %H:%M")
grep -E "$since" /var/log/app.log | grep -i "error"
}
# 사용법: recent_errors 30 (최근 30분)
설명
이것이 하는 일: Bash 함수는 여러 줄의 명령어를 하나의 이름으로 묶어서, 마치 새로운 명령어를 만드는 것처럼 동작합니다. 함수를 호출하면 그 안에 정의된 모든 명령어가 순서대로 실행됩니다.
첫 번째로, search_errors() 함수를 자세히 봅시다. local date=$1은 함수를 호출할 때 전달된 첫 번째 인자를 date 변수에 저장합니다.
예를 들어 search_errors 2024-11-18을 실행하면 $1에는 "2024-11-18"이 들어갑니다. local 키워드는 이 변수가 함수 안에서만 유효하다는 뜻으로, 다른 스크립트와 충돌하지 않도록 보호합니다.
왜 이렇게 할까요? 전역 변수로 만들면 나중에 다른 함수나 스크립트에서 같은 이름을 쓸 때 문제가 생기기 때문입니다.
그 다음으로, 함수 안에서 파이프를 사용한 부분을 봅시다. `grep "$date" ...
| grep -i "error" | tee "errors_$date.txt"`는 우리가 앞에서 배운 모든 기술을 조합한 것입니다. 날짜로 필터링하고, 에러를 찾고, 화면에 출력하면서 동시에 파일로 저장합니다.
마지막 줄의 echo "Results saved..."는 사용자에게 어디에 저장됐는지 알려줍니다. 이런 친절한 메시지가 실무에서는 아주 중요합니다.
더 고급 함수인 search_logs()는 세 개의 매개변수를 받습니다. local date=$1, local level=$2, local keyword=$3처럼 여러 인자를 순서대로 받을 수 있습니다.
output_file="logs_${date}_${level}.txt"는 변수들을 조합해서 동적으로 파일명을 생성합니다. 예를 들어 search_logs 2024-11-18 ERROR payment를 실행하면 "logs_2024-11-18_ERROR.txt"라는 파일이 만들어집니다.
${variable} 형식은 변수를 다른 텍스트와 붙일 때 사용하는 문법입니다. 백슬래시(\)는 긴 명령어를 여러 줄로 나눌 때 사용합니다.
Bash는 보통 엔터를 치면 명령어가 끝났다고 생각하는데, \를 쓰면 "아직 안 끝났어, 다음 줄도 이어서 읽어"라고 알려줍니다. 이렇게 하면 가독성이 훨씬 좋아집니다.
한 줄에 다 쓰면 화면을 벗어나서 읽기 어렵거든요. recent_errors() 함수는 더 실용적입니다.
${1:-10}은 "첫 번째 인자가 있으면 그것을 쓰고, 없으면 10을 기본값으로 써"라는 뜻입니다. 따라서 recent_errors만 치면 최근 10분, recent_errors 30을 치면 최근 30분을 검색합니다.
date -d "$minutes minutes ago"는 현재 시각에서 N분을 뺀 시각을 계산합니다. 이렇게 동적으로 시간 범위를 계산하면 "최근에 뭔 일이 있었지?"라는 질문에 빠르게 답할 수 있습니다.
여러분이 이런 함수를 ~/.bashrc 파일에 추가해두면 터미널을 열 때마다 자동으로 로드됩니다. 즉, 한 번 정의하면 평생 쓸 수 있다는 뜻입니다.
팀에서 사용하려면 함수들을 모아서 log_utils.sh 같은 파일로 만들고, 모두가 source log_utils.sh로 불러오도록 하면 됩니다. 이렇게 하면 팀 전체가 동일한 방식으로 로그를 분석할 수 있어 협업이 훨씬 쉬워집니다.
실전 팁
💡 함수에 도움말을 추가하세요. 함수 시작 부분에 # Usage: search_errors <date> 같은 주석을 달아두면 나중에 "어떻게 쓰는 거였지?" 할 때 바로 확인할 수 있습니다.
💡 인자 검증을 추가하면 안전합니다. if [ -z "$1" ]; then echo "Error: Date required"; return 1; fi처럼 필수 인자가 없으면 에러 메시지를 출력하세요.
💡 함수를 테스트할 때는 set -x를 함수 첫 줄에 추가하세요. 그러면 실행되는 모든 명령어가 화면에 표시되어 디버깅이 쉬워집니다. 테스트가 끝나면 제거하세요.
💡 자주 쓰는 로그 파일 경로는 변수로 빼세요. LOG_DIR="/var/log", grep "$date" "$LOG_DIR/app.log"처럼 하면 경로가 바뀔 때 한 곳만 수정하면 됩니다.
💡 복잡한 함수는 여러 개의 작은 함수로 나누세요. search_by_date(), filter_by_level(), save_results() 같이 기능별로 분리하면 재조합이 쉽고 테스트도 쉬워집니다.