리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.

리눅스 시스템 기초 23 / 50 · Part 3. Linux 명령어 활용
실습 환경: Rocky Linux 9 (10.0.0.200) · 21편 실습용 샘플 로그
이전 글: 22. find로 파일 검색

1. 들어가며

head와 tail은 파일의 앞부분과 뒷부분을 보여주는 단순한 명령어다. 하지만 관제 업무에서는 사용 빈도가 매우 높다.

  • 처음 보는 로그 파일의 형식과 시작 시각 을 확인할 때 → head
  • 가장 최근 이벤트 를 볼 때 → tail
  • 공격이 진행 중인지 실시간으로 지켜볼 때 → tail -f / tail -F

특히 마지막 항목에는 잘 알려지지 않은 함정이 있다. 로그 로테이션이 일어나면 tail -f는 새 로그를 보지 못한다. 이번 글은 이 차이를 14편의 inode 개념으로 설명하고, 실제로 재현한 결과를 보여준다.


2. 핵심 개념

2-1. head

명령의미
head file앞 10줄
head -n 20 file앞 20줄
head -n -5 file마지막 5줄을 제외한 전부
head -c 64 file앞 64바이트 (바이너리 헤더 확인)

2-2. tail

명령의미
tail file끝 10줄
tail -n 50 file끝 50줄
tail -n +100 file100번째 줄부터 끝까지
tail -f file끝을 보여준 뒤 추가되는 내용을 계속 출력 (파일 디스크립터 추적)
tail -F file-f + 파일이 사라지거나 바뀌면 같은 이름으로 다시 열기 (--follow=name --retry)
tail -n0 -F file기존 내용 없이 새로 들어오는 줄만

2-3. 비슷한 역할의 도구

도구용도
less +F file실시간 추적 중 Ctrl+C로 멈추고 검색(/패턴), F로 재개
journalctl -fsystemd 저널 실시간 추적 (48편)
sed -n '100,120p'특정 줄 범위 (26편)
wc -l전체 줄 수

3. 동작 원리

tail -f 와 tail -F — 로그 로테이션에서 갈리는 차이

3-1. 로그 로테이션

로그 파일이 무한히 커지지 않도록 logrotate가 주기적으로 파일을 교체한다. Rocky Linux의 기본 방식은 다음과 같다.

1. secure        →  secure-20260923   (이름 변경, inode 그대로)
2. 새 secure 생성                      (새 inode)
3. rsyslog에 알림 → 새 파일에 기록 시작

3-2. -f와 -F의 차이

  • tail -f는 처음 연 파일 디스크립터(= inode) 를 계속 읽는다. 이름이 secure-20260923으로 바뀌어도 같은 파일을 보고 있으므로, 새 secure에 쌓이는 로그는 보이지 않는다.
  • tail -F는 주기적으로 이름(경로) 을 다시 확인한다. 이름이 가리키는 inode가 바뀌면 새 파일을 다시 연다.

아래는 이 상황을 재현한 실제 결과다.

echo line1 > app.log
tail -f app.log > f.out &          # A: -f
tail -F app.log > F.out &          # B: -F
echo line2 >> app.log
mv app.log app.log.1                # 로테이션: 이름 변경
echo line3-old >> app.log.1         # 옛 파일에 늦게 기록
echo new1 > app.log                 # 새 파일 생성
echo new2 >> app.log
== tail -f 결과
line1
line2
line3-old

== tail -F 결과
line1
line2
tail: 'app.log' has become inaccessible: No such file or directory
tail: 'app.log' has appeared;  following new file
new1
new2

-f는 로테이션 이후의 new1, new2를 놓쳤다. 새벽 로그 로테이션 시각에 모니터링 창이 "조용해지는" 이유가 이것이다. 실시간 감시에는 -F를 쓴다.


4. 실습

cd ~/lab

# 1) 형식과 시작 시각 확인
head -n 3 secure

# 2) 가장 최근 이벤트
tail -n 3 secure

# 3) 특정 줄 번호 주변만 보기 (grep -n으로 위치 확인 후)
grep -n "Accepted password" secure
tail -n +127 secure | head -n 5

# 4) 바이너리 앞부분 (13편 매직 넘버)
head -c 4 /usr/bin/ls | od -An -tx1

# 5) 실제 서버 실시간 감시
sudo tail -n0 -F /var/log/secure | grep --line-buffered -E "Failed|Accepted|sudo"

마지막 명령의 --line-buffered는 grep이 결과를 모아 두지 않고 줄마다 바로 출력 하게 한다. 파이프 뒤에서 실시간 출력이 늦게 나오는 문제를 막는다.


5. 결과 분석

샘플 로그에 실행한 실제 결과다.

① head -n 3 secure

Sep 23 01:47:45 rocky sshd[3125]: Failed password for root from 203.0.113.150 port 51662 ssh2
Sep 23 02:10:09 rocky sshd[3138]: Failed password for root from 10.0.0.128 port 59219 ssh2
Sep 23 02:10:18 rocky sshd[3141]: Failed password for root from 10.0.0.128 port 40083 ssh2

로그 형식(RHEL syslog), 호스트명(rocky), 첫 기록 시각(01:47) 을 확인할 수 있다. 사고 시각이 이보다 이전이라면 이 파일이 아니라 이전 로테이션 파일 을 봐야 한다.

② tail -n +127 secure | head -n 5

Sep 23 02:19:21 rocky sshd[5224]: Accepted password for devuser from 10.0.0.128 port 51514 ssh2
Sep 23 02:19:21 rocky sshd[5224]: pam_unix(sshd:session): session opened for user devuser(uid=1001) by (uid=0)
Sep 23 02:21:21 rocky sudo[5233]:  devuser : user NOT in sudoers ; TTY=pts/1 ; PWD=/home/devuser ; USER=root ; COMMAND=/bin/bash
Sep 23 02:22:21 rocky su[5270]: pam_unix(su-l:auth): authentication failure; logname=devuser uid=1001 euid=0 tty=pts/1 ruser=devuser rhost=  user=root
Sep 23 02:22:21 rocky su[5270]: FAILED SU (to root) devuser on pts/1

grep으로 위치를 찾고, tail -n +N | head로 그 지점부터의 흐름 을 읽는 방식은 긴 로그에서 사건 구간을 잘라 보는 가장 간단한 방법이다.


6. 보안 관점

상황위험대응
tail -f로 감시 중 로테이션새 이벤트를 놓침tail -F, journalctl -f
로그 수집기의 로테이션 처리수집 누락 → SIEM 공백수집기의 inode·경로 추적 설정 확인
파일 시작 시각 미확인사고 시점 로그가 이전 파일에 있는데 "로그 없음"으로 판단head -n1로 시작 시각 확인, secure-* 검색
공격자의 로그 비우기: > secure로 크기 0 → tail 결과 없음크기·ctime 확인(11편), SIEM 사본 비교
대용량 로그를 cat터미널 멈춤, 분석 서버 부하head, tail, less로 필요한 부분만

7. SOC / 보안관제 활용

7-1. 실시간 감시 조합

# 인증 이벤트만 실시간 (Ubuntu: /var/log/auth.log)
sudo tail -n0 -F /var/log/secure | grep --line-buffered -E "Accepted|Failed|NOT in sudoers|FAILED SU"

# 웹 공격 패턴만 실시간
sudo tail -n0 -F /var/log/httpd/access_log | grep --line-buffered -Ei '\.\./|union.*select|%27'

# systemd 저널 (sshd 유닛)
sudo journalctl -f -u sshd

7-2. 사고 구간 잘라 보기

# 1) 핵심 이벤트의 줄 번호
n=$(grep -n "Accepted password for devuser" secure | cut -d: -f1)
# 2) 그 앞 20줄 ~ 뒤 30줄
tail -n +$((n-20)) secure | head -n 50

7-3. 분석 흐름

[Alert]   SIEM: 02:10 이후 10.0.0.128 로그인 실패 급증
   ↓
[현장 확인] tail -n0 -F secure | grep 10.0.0.128  → 공격이 아직 진행 중인가?
   ↓
[과거 확인] head -n1 secure → 파일 시작 시각이 사고 이전인지 확인
          이전이 아니면 secure-YYYYMMDD 도 검색
   ↓
[구간 추출] grep -n → tail -n +N | head → 성공·권한 상승 시도 흐름
   ↓
[판단]    진행 중이면 즉시 차단, 종료됐으면 성공 이후 행위 조사로 전환

8. 핵심 정리

  • head는 형식·시작 시각 확인, tail은 최근 이벤트 확인에 쓴다.
  • tail -n +N은 N번째 줄부터, head -n -N은 마지막 N줄 제외다.
  • tail -f는 inode 를, tail -F는 이름 을 따라간다. 로그 로테이션 이후에는 -F만 새 로그를 본다.
  • 파이프 뒤 grep은 --line-buffered로 실시간 출력한다.
  • "로그가 없다"고 판단하기 전에 파일 시작 시각과 로테이션 파일 을 확인한다.

9. 다음 글

다음 글 「24. sort와 uniq」 에서는 21편부터 계속 써 온 sort | uniq -c | sort -rn 패턴을 분해한다. 정렬 기준(-k, -t, -n), uniq가 정렬된 입력을 요구하는 이유, 그리고 IP·계정·URL의 Top N을 뽑아 탐지 임계치의 근거 를 만드는 방법을 다룬다.


참고 자료

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글