Step 20. 검색 (grep/find) — 건초더미에서 바늘 두 개 찾기
Level 0 — 컴퓨터 조작과 구조의 이해 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간
전제: Step 19 완료. 가상머신 속 우분투 터미널에서 진행합니다.
- 준비물: 우분투 가상머신, 터미널. Step 18~19의 명령들이 오늘 전부 재료로 쓰입니다.
- 주의: 오늘 실습은 읽기와 검색만이라 100% 안전합니다. 결과가 많이 나오는 챕터인데 겁내지 마세요 — 많이 나오는 법을 배운 뒤에 줄이는 법도 배웁니다.
- 실측 안내: 이 챕터의 출력은 Ubuntu 24.04에서 실제 실행한 결과입니다. 실측은 관리자(root) 계정으로 진행되어 경로에
/root가 보이고, 권한 오류 실험(3-2절)은 일반 계정의 처지를 재현하기 위해 권한 없는 계정으로 따로 실행해 확인했습니다. 여러분의 VM에서는 홈이/home/사용자이름으로 보입니다.
수사관이 사건 현장에서 던지는 질문은 두 종류입니다: "이름이 ○○인 사람을 찾아라"(신원으로 찾기)와 "이런 말을 한 사람을 찾아라"(내용으로 찾기). 파일 시스템도 같습니다. 오늘의 두 주인공 — grep은 내용으로, find는 이름·조건으로 찾습니다. Step 4에서 Select-String으로 로그를 뒤졌던 것, 기억하시죠? grep은 그것의 리눅스 원조이자, 전 세계 보안 분석가가 매일 수백 번 치는 명령입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- grep(내용 검색)과 find(이름·조건 검색)의 역할 차이를 설명한다
grep -r,-i,-n옵션으로 폴더째 내용 검색을 한다find로 이름·크기·수정 시간 조건 검색을 한다2>/dev/null의 각 부분을 수도관 지식으로 해독하고 쓸 수 있다- find와 grep을 파이프로 연결해 "좁히고, 또 좁히는" 수사를 한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 우분투 리눅스 터미널 (bash) |
| 오늘의 명령어 | grep -r/-i/-n(내용 검색), find -name/-size/-mtime(조건 검색), 2>/dev/null(오류 버리기), history | grep(과거 명령 찾기) |
| 필요한 개념 | 와일드카드(*), 표준 스트림 번호(Step 19), /dev/null, 파이프 연쇄 |
2-1. grep — 문서 안의 돋보기
grep의 기본형: grep "찾을말" 파일. 그 파일에서 "찾을말"이 포함된 줄을 보여 줍니다.
핵심 옵션 세 개:
| 옵션 | 의미 |
|---|---|
-r |
폴더째로 뒤진다 (재귀 — 하위 폴더 전부) |
-i |
대소문자 무시 (Password도 PASSWORD도) |
-n |
줄 번호도 함께 표시 |
이름의 유래가 재미있습니다 — 유닉스의 고대 텍스트 에디터 명령 "globally search a regular expression and print"(정규식으로 전체를 검색해 출력해라)의 앞글자입니다. 정규식은 Level 1에서 정식으로 배우고, 오늘은 "글자 그대로 검색"만으로도 충분히 강력합니다.
2-2. find — 조건 수사관
find의 기본형: find 어디서 -조건 값. 예시로 감을 잡으세요:
| 명령 | 의미 |
|---|---|
find ~ -name "*.txt" |
내 집 아래서 이름이 .txt로 끝나는 것 |
find ~ -size +10M |
10MB보다 큰 것 (+는 "초과", -는 "미만") |
find ~ -mtime -1 |
최근 하루(-1) 안에 수정된 것 |
보안 관점에서 find의 묘미: "최근에 바뀐 파일" 검색은 침해 조사의 고전입니다 — 공격 직후에 시스템의 어느 파일이 건드려졌는지 찾는 기술. "수상하게 큰 파일" 찾기는 데이터 유출 흔적을 찾는 기술입니다.
2-3. 2>/dev/null 해독 — 오늘의 마법 주문
시스템 전체를 검색하면 읽을 권한이 없는 폴드에서 "Permission denied(권한 거부)" 오류가 무더기로 뿜어져 나와 결과가 오류에 묻힙니다. 그때 쓰는 관용구가 2>/dev/null입니다. Step 19의 지식으로 해독해 봅시다:
2— 오류 수도관 (stderr의 번호)>— 방향 바꾸기 (리다이렉션)/dev/null— 리눅스의 쓰레기통 파일 (들어간 것은 전부 사라짐)
합치면: "오류는 쓰레기통으로 보내고, 정상 결과만 보여 줘." /dev/null은 "모든 것은 파일" 철학의 재미있는 실례이기도 합니다 — 쓰레기통조차 파일입니다.
2-4. 언제 어느 쪽을 쓰는가
| 상황 | 도구 |
|---|---|
| 파일이름을 알거나 일부 안다 | find |
| 파일내용의 일부를 안다 | grep |
| 시간·크기 같은 속성으로 찾는다 | find |
| 로그에서 특정 단어가 있는 줄이 궁금하다 | grep |
실전에서는 둘을 연결합니다: find로 후보 파일을 좁히고, 그 안에서 grep으로 내용을 찾는 식입니다.
3. 따라 하기
3-1. grep 기본 — 한 파일에서 찾기
grep "root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
(2026-09-09 실측.)
출력 읽는 법: /etc/passwd에서 "root"가 포함된 줄입니다. 계정 파일이니 root 줄이 나오는 게 당연하죠. 이번엔 대소문자 실험:
grep -i "ROOT" /etc/passwd
root:x:0:0:root:/root:/bin/bash
(2026-09-09 실측.)
같은 결과가 나옵니다. -i가 대소문자를 무시했기 때문입니다. 검색할 때 대소문자를 모르면 -i를 붙이는 게 안전합니다.
3-2. grep -r — 폴더째 뒤지기 (그리고 권한 오류)
grep -r "password" /etc 2>/dev/null | head -6
/etc/default/useradd:# The number of days after a password expires until the account
/etc/ssl/openssl.cnf:# input_password = secret
/etc/ssl/openssl.cnf:# output_password = secret
/etc/ssl/openssl.cnf:challengePassword = A challenge password
/etc/pam.d/su:# This allows root to su without passwords (normal operation)
/etc/pam.d/su:# su without a password.
(2026-09-09 실측. 환경에 따라 결과가 다릅니다.)
출력 읽는 법: 파일이름:내용 형식 — 어느 파일의 어느 줄인지가 앞에 붙습니다. /etc 전체를 뒤져서 "password"를 포함한 줄을 찾은 겁니다.
그런데 2>/dev/null을 빼고 일반 사용자로 실행하면 어떻게 될까요? 실측해 봤습니다 (권한 없는 계정으로 실행):
grep: /etc/shadow-: Permission denied
grep: /etc/sudoers: Permission denied
grep: /etc/gshadow: Permission denied
grep: /etc/landscape/client.conf: Permission denied
(2026-09-09 실측 — root가 아닌 계정으로 grep -r "password" /etc를 실행했을 때의 실제 오류 메시지입니다. 여러분의 VM 계정으로 그냥 실행하면 이 광경을 직접 볼 수 있습니다.)
2>/dev/null 덕분에 3-2절 본문에서는 이 오류들이 한 줄도 안 나온 겁니다. 빼고 실행 → 오류 무더기, 붙이고 실행 → 결과만. 이 대비가 확실해야 습관이 됩니다.
왜 하는가: 이 한 줄이 "설정 파일 어딘가에 있는 비밀번호 관련 설정 찾기"입니다. 침해 조사에서 "공격자가 남긴 문자열을 전체에서 검색"할 때도 정확히 이 형태를 씁니다.
3-3. find 기본 — 이름으로 찾기
find /etc -name "*.conf" 2>/dev/null | head
/etc/pam.conf
/etc/modules-load.d/modules.conf
/etc/PackageKit/PackageKit.conf
/etc/PackageKit/Vendor.conf
/etc/landscape/client.conf
/etc/rsyslog.conf
(2026-09-09 실측. head는 기본 10줄 — 실측에서는 6줄만 나온 뒤 목록이 다했거나, 여러분 환경에서는 더 많이 나올 수 있습니다.)
읽는 법: /etc 아래에서 이름이 .conf로 끝나는 파일들. *.conf의 *는 "아무 글자나"라는 와일드카드(wildcard)입니다(Step 2에서 만난 그것 — 리눅스에도 있습니다). 뒤의 | head는 "앞부분만" — 결과가 많을 때 슬쩍 보는 Step 19의 기술입니다.
.conf 파일이 뭔가요: configuration(설정)의 줄임말 — 리눅스 설정 파일은 대부분 이 확장자를 달고 /etc에 모여 있습니다. 레지스트리(Step 11)라는 전용 DB를 쓰는 윈도우와 달리, 리눅스 설정은 찾고, 읽고, 백업하기 쉬운 텍스트입니다.
3-4. find 속성 검색 — 크기와 시간
find ~ -mtime -1 2>/dev/null | head
/root
/root/stream-lab
/root/stream-lab/filelist.txt
/root/stream-lab/a.txt
/root/stream-lab/live.log
/root/practice
/root/practice/file2.txt
...
(2026-09-09 실측 — Step 18~19에서 만든 파일들이 줄줄이 나옵니다. 여러분의 결과도 오늘 만든 것들로 채워질 겁니다.)
최근 하루 안에 수정된 파일들입니다. 크기 조건도 해 보세요:
find ~ -size +1M 2>/dev/null
내 홈에서 1MB보다 큰 파일들. (연습용 VM이라 없을 수도 있습니다 — 없으면 -size +100k로 기준을 낮춰 보세요. 실측 환경에서는 캐시 파일 두 개가 걸렸습니다.)
조사관의 눈으로 읽기: "오늘 하루 동안 이 컴퓨터에서 바뀐 파일 목록" — 이게 타임라인 조사의 리눅스 버전입니다. Step 12의 이벤트 로그 타임라인과 같은 사고방식, 다른 도구입니다.
3-5. history 검색 — 내 과거에서 찾기
history | grep find
1 find /etc -name "*.conf" 2>/dev/null | head
3 find ~ -mtime -1
(2026-09-09 실측 — find 명령들만 골라낸 모습입니다. 여러분의 출력은 오늘 친 순서와 번호가 그대로 찍힙니다.)
읽는 법: Step 18에서 본 명령 기록(history)을 파이프로 grep에 흘려서, "find를 썼던 명령들"만 골라낸 겁니다. "그 명령 뭐였더라?"를 찾는 실전 최고 팁입니다. 긴 명령을 또 치지 말고 이렇게 찾으세요.
예측해 보기:
grep -r "root" /etc 2>/dev/null | wc -l은 무엇을 출력할까요? (정답: "root"가 포함된 줄의 개수. 실측 환경에서는 272가 나왔습니다. "몇 군데에 있는가"를 세는 이 패턴은 로그에서 이벤트 건수를 재는 표준 기술입니다. 직접 확인해 보세요 — 여러분 환경의 숫자는 다릅니다.)
3-6. grep에도 줄 번호를 — -n 옵션
검색 결과가 길 때 "몇 번째 줄인지"가 중요해집니다:
grep -n "root" /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
(2026-09-09 실측.)
읽는 법: 줄 맨 앞의 숫자가 줄 번호입니다. "1번째 줄에서 발견"이라는 좌표가 붙었습니다. 나중에 "이 파일의 25번째 줄을 고쳐라" 같은 지시를 받거나, 로그에서 사건의 위치를 특정할 때 이 번호가 좌표가 됩니다. 습관처럼 붙여 두면 손해 볼 것 없는 옵션입니다.
3-7. 두 도구의 협업 — find 후 grep
실전 패턴 하나를 경험해 봅시다. "최근 하루 안에 바뀐 파일들 중에서 이름에 log가 들어가는 것":
find ~ -mtime -1 2>/dev/null | grep log
/root/stream-lab/live.log
/root/test-note.log
(2026-09-09 실측 — 실측 환경에서는 Step 19의 live.log와 실험용 test-note.log가 걸렸습니다. 여러분 환경에서는 Step 19에서 만든 live.log가 보일 겁니다.)
구조 읽기: find가 시간으로 1차 수사 → grep이 이름으로 2차 수사. 도구 두 개가 파이프로 연결되어 수사를 이어갑니다. 이 "좁히고, 또 좁히기"가 검색의 기본 전술입니다.
4. 미션과 연습문제
미션 — 흔적 찾기 게임
준비물부터 만듭니다:
mkdir -p ~/search-game/deep/nested
echo "이 파일에는 비밀 토큰 hunter2 가 있습니다" > ~/search-game/deep/nested/clue.txt
echo "아무것도 없는 파일" > ~/search-game/normal.txt
이제 수사관이 되세요:
- 임무 1:
clue.txt를 이름만으로 찾으세요 (find 사용) - 임무 2: "hunter2"라는 문자열이 든 파일을 내용으로 찾으세요 (grep -r 사용)
- 임무 3: 오늘(최근 하루) 내 홈에서 바뀐 파일을 전부 나열하세요
- 임무 4:
history | grep grep으로 오늘 내가 grep을 몇 번 썼는지 세어 보세요 (힌트: 뒤에| wc -l)
스스로 검증하는 법: 임무 1과 2가 같은 파일을 가리키면 정답입니다. 두 도구가 서로 다른 경로로 같은 바늘을 찾아내는 것 — 이 대조가 오늘 챕터의 요약입니다.
연습문제
문제 1. 다음 상황에 grep과 find 중 어느 쪽을 써야 할까요? ① 이름에 config가 들어가는 파일 찾기 ② "ERROR"라는 글자가 포함된 줄 찾기 ③ 최근 1일 안에 바뀐 파일 찾기
문제 2. 2>/dev/null을 2, >, /dev/null 세 부분으로 나눠 각각의 의미를 설명해 보세요.
문제 3. find ~ -name *.txt처럼 패턴에 따옴표를 빼면 왜 문제가 생길까요? 누가 *를 먼저 해석해 버리는 걸까요?
문제 4. 로그에서 "error"를 검색했는데 분명 있는 단어인데 결과가 0건입니다. 가장 먼저 의심할 것과 해결 옵션은 무엇일까요?
5. 모범 답안과 완료 기준
미션 모범 답안
# 임무 1 — 이름으로
find ~ -name "clue.txt" 2>/dev/null
/root/search-game/deep/nested/clue.txt
# 임무 2 — 내용으로
grep -r "hunter2" ~/search-game 2>/dev/null
/root/search-game/deep/nested/clue.txt:이 파일에는 비밀 토큰 hunter2 가 있습니다
(2026-09-09 실측 — 여러분 환경에서는 /root 자리에 /home/사용자이름이 보입니다.)
# 임무 3 — 최근 하루
find ~ -mtime -1 2>/dev/null
# 임무 4 — grep 사용 횟수
history | grep grep | wc -l
검증하는 법: ① 임무 1과 2가 같은 경로(.../deep/nested/clue.txt)를 가리키는가 — 두 도구가 서로 다른 경로로 같은 바늘을 찾았다면 완성입니다. ② 임무 3의 목록에 방금 만든 search-game 파일들이 포함되는가. ③ 임무 4의 숫자가 실제로 친 grep 횟수와 비슷한가(history에는 방금 친 검색 명령 자체도 포함됩니다).
심화로 생각해 볼 것: 공격자가 침투 후 "password"라는 단어가 든 파일을 찾을 때 쓰는 명령은 오늘 배운 grep -r 그대로입니다. 그리고 수비자의 탐지 단서는 Step 18의 .bash_history와 Step 12의 로그에 있습니다 — 공격과 수비가 같은 명령 위에서 만납니다.
연습문제 해답
문제 1 해답. ① find (이름 조건) ② grep (내용) ③ find (시간 속성). "이름·크기·시간이면 find, 내용이면 grep"이 구분의 전부입니다.
문제 2 해답. 2는 표준 오류(stderr) 수도관의 번호, >는 출력 방향을 바꾸는 리다이렉션, /dev/null은 들어간 것이 전부 사라지는 쓰레기통 파일. 합치면 "오류는 버리고 정상 결과만 보여 줘"입니다.
문제 3 해답. 셸(bash)이 find보다 먼저 *를 해석해 버립니다 — 현재 디렉토리의 파일 이름들로 치환해 버려서 find에는 의도와 다른 내용이 전달됩니다. "*.txt"처럼 따옴표로 감싸면 패턴이 find에게 그대로 배달됩니다. "패턴은 find에게 직접 전달한다" — 따옴표가 그 배달원입니다.
문제 4 해답. 대소문자 불일치를 먼저 의심합니다 — 리눅스는 Error와 error를 다르게 봅니다. grep -i로 대소문자를 무시하세요. 로그 검색에서는 대소문자가 제멋대로라서 -i가 거의 기본 옵션입니다.
완료 기준 체크리스트
- [ ] grep과 find의 차이(내용 vs 이름·조건)를 설명할 수 있다
- [ ]
grep -r로 폴더째 내용 검색을 할 수 있다 - [ ]
-i(대소문자 무시)와-n(줄 번호) 옵션을 쓸 수 있다 - [ ]
2>/dev/null의 각 부분(2, >, /dev/null)을 설명할 수 있다 - [ ] find로 이름·크기·시간 조건 검색을 할 수 있다
- [ ]
history | grep으로 과거 명령을 찾을 수 있다 - [ ] find와 grep을 파이프로 연결해 봤다
- [ ] 미션: 흔적 찾기 게임 4개 임무를 완수했다
6. 흔한 실수와 해결
벽 1. grep을 쳤는데 오류가 화면을 도배한다
증상: grep: /etc/shadow: Permission denied 류가 수십 줄.
원인: 권한 없는 폴드를 검색 중 — 정상 현상이고, 그래서 관용구가 있습니다 (3-2절 실측 참고).
해결: 명령 끝에 2>/dev/null을 붙이세요. 그리고 그게 왜 작동하는지 2-3절을 다시 읽어 보세요 — 이해한 도구는 잊히지 않습니다.
벽 2. find에서 -name 뒤의 패턴이 안 먹힌다
증상: find ~ -name *.txt가 이상한 결과나 오류를 냅니다.
원인: *는 따옴표로 감싸야 합니다. 안 감싸면 셸이 먼저 해석해 버려서 find에 다른 뜻으로 전달됩니다.
해결: 항상 "*.txt"처럼 따옴표로 감싸세요.
벽 3. grep 결과가 "이진 파일입니다"라고만 나온다
증상: 내용 대신 "binary file matches"류 메시지.
원인: 검색 대상에 문서가 아닌 프로그램 파일(이진 파일)이 섞여 있고, 거기서 우연히 글자가 맞은 것입니다.
해결: 지금 단계에서는 무시해도 됩니다. "텍스트가 아닌 파일에서도 글자 조각은 맞을 수 있다"는 것만 알아두세요. 이진 파일 분석은 훨씬 뒤(Level 3)의 이야기입니다.
벽 4. find가 너무 오래 걸린다
증상: find /를 했더니 한참을 안 끝납니다.
원인: 시스템 전체(수십만 파일)를 뒤지는 중 — 정상입니다.
해결: Ctrl + C로 멈추고, 검색 범위를 좁히세요. /etc, ~처럼 "있을 만한 곳"부터 보는 것이 수사의 순서입니다. 범위 좁히기가 곧 수사력입니다.
벽 5. 검색은 되는데 대소문자 때문에 못 찾았다
증상: 분명 있는 단어인데 결과 0건.
원인: 대소문자 불일치. 리눅스는 Error와 error를 다르게 봅니다.
해결: grep -i 습관화. 특히 로그 검색에서는 -i가 거의 기본 옵션입니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| grep | 내용으로 줄을 찾는 돋보기 |
| find | 이름·크기·시간 조건으로 파일을 찾는 수사관 |
와일드카드(*) |
"아무 글자나" — find에는 따옴표로 감싸 전달 |
/dev/null |
리눅스의 쓰레기통 파일 — 오류를 버리는 곳 |
| 좁히기 전술 | find로 1차, grep으로 2차 — 수사는 연쇄로 |
오늘의 명령어
| 명령 | 하는 일 |
|---|---|
grep -rin "말" 폴더 |
폴더째·대소문자 무시·줄 번호 포함 내용 검색 |
find 폴더 -name "패턴" |
이름으로 찾기 |
find 폴더 -size +10M / -mtime -1 |
크기 / 최근 수정 시간으로 찾기 |
명령 2>/dev/null |
오류는 쓰레기통으로 |
grep ... | wc -l |
몇 건인가 세기 |
find ... | grep ... |
1차 수사 후 2차 수사 |
history | grep 단어 |
그 명령 뭐였더라? |
명령어보다 중요한 감각
침해 조사의 대부분은 결국 "찾기"입니다 — 수상한 문자열을 찾고, 최근 바뀐 파일을 찾고, 특정 시간의 기록을 찾고. 실제 분석 현장의 하루는 "의심스러운 IP를 grep → 그 IP가 한 일을 grep → 그 시간대의 다른 기록을 grep"처럼, 검색의 결과가 다음 검색의 단서가 되는 사슬입니다. 오늘 배운 것은 그 사슬의 첫 고리입니다.
두 가지를 더 기억해 두세요. 첫째, "설치된 프로그램이 어디 있는가"를 묻는 전용 명령 which python3 같은 것도 있습니다 — find보다 빠른 지름길입니다. 둘째, 실무자들은 잘 만든 검색 명령을 개인 노트에 모아 둡니다. 검색 실력은 결국 "질문을 좁히는 능력"이고, 그 추리가 정확해지려면 시스템 구조를 알아야 합니다 — 검색 실력은 곧 시스템 이해의 척도입니다.
전부 체크되면 Step 20 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.