리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 23 / 50 · Part 3. Linux 명령어 활용
실습 환경: Rocky Linux 9 (10.0.0.200) · 21편 실습용 샘플 로그
이전 글: 22. find로 파일 검색
head와 tail은 파일의 앞부분과 뒷부분을 보여주는 단순한 명령어다. 하지만 관제 업무에서는 사용 빈도가 매우 높다.
headtailtail -f / tail -F특히 마지막 항목에는 잘 알려지지 않은 함정이 있다. 로그 로테이션이 일어나면 tail -f는 새 로그를 보지 못한다. 이번 글은 이 차이를 14편의 inode 개념으로 설명하고, 실제로 재현한 결과를 보여준다.
| 명령 | 의미 |
|---|---|
head file | 앞 10줄 |
head -n 20 file | 앞 20줄 |
head -n -5 file | 마지막 5줄을 제외한 전부 |
head -c 64 file | 앞 64바이트 (바이너리 헤더 확인) |
| 명령 | 의미 |
|---|---|
tail file | 끝 10줄 |
tail -n 50 file | 끝 50줄 |
tail -n +100 file | 100번째 줄부터 끝까지 |
tail -f file | 끝을 보여준 뒤 추가되는 내용을 계속 출력 (파일 디스크립터 추적) |
tail -F file | -f + 파일이 사라지거나 바뀌면 같은 이름으로 다시 열기 (--follow=name --retry) |
tail -n0 -F file | 기존 내용 없이 새로 들어오는 줄만 |
| 도구 | 용도 |
|---|---|
less +F file | 실시간 추적 중 Ctrl+C로 멈추고 검색(/패턴), F로 재개 |
journalctl -f | systemd 저널 실시간 추적 (48편) |
sed -n '100,120p' | 특정 줄 범위 (26편) |
wc -l | 전체 줄 수 |

로그 파일이 무한히 커지지 않도록 logrotate가 주기적으로 파일을 교체한다. Rocky Linux의 기본 방식은 다음과 같다.
1. secure → secure-20260923 (이름 변경, inode 그대로)
2. 새 secure 생성 (새 inode)
3. rsyslog에 알림 → 새 파일에 기록 시작
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를 쓴다.
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이 결과를 모아 두지 않고 줄마다 바로 출력 하게 한다. 파이프 뒤에서 실시간 출력이 늦게 나오는 문제를 막는다.
샘플 로그에 실행한 실제 결과다.
① 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로 그 지점부터의 흐름 을 읽는 방식은 긴 로그에서 사건 구간을 잘라 보는 가장 간단한 방법이다.
| 상황 | 위험 | 대응 |
|---|---|---|
tail -f로 감시 중 로테이션 | 새 이벤트를 놓침 | tail -F, journalctl -f |
| 로그 수집기의 로테이션 처리 | 수집 누락 → SIEM 공백 | 수집기의 inode·경로 추적 설정 확인 |
| 파일 시작 시각 미확인 | 사고 시점 로그가 이전 파일에 있는데 "로그 없음"으로 판단 | head -n1로 시작 시각 확인, secure-* 검색 |
| 공격자의 로그 비우기 | : > secure로 크기 0 → tail 결과 없음 | 크기·ctime 확인(11편), SIEM 사본 비교 |
대용량 로그를 cat | 터미널 멈춤, 분석 서버 부하 | head, tail, less로 필요한 부분만 |
# 인증 이벤트만 실시간 (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
# 1) 핵심 이벤트의 줄 번호
n=$(grep -n "Accepted password for devuser" secure | cut -d: -f1)
# 2) 그 앞 20줄 ~ 뒤 30줄
tail -n +$((n-20)) secure | head -n 50
[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 → 성공·권한 상승 시도 흐름
↓
[판단] 진행 중이면 즉시 차단, 종료됐으면 성공 이후 행위 조사로 전환
head는 형식·시작 시각 확인, tail은 최근 이벤트 확인에 쓴다.tail -n +N은 N번째 줄부터, head -n -N은 마지막 N줄 제외다.tail -f는 inode 를, tail -F는 이름 을 따라간다. 로그 로테이션 이후에는 -F만 새 로그를 본다.--line-buffered로 실시간 출력한다.다음 글 「24. sort와 uniq」 에서는 21편부터 계속 써 온 sort | uniq -c | sort -rn 패턴을 분해한다. 정렬 기준(-k, -t, -n), uniq가 정렬된 입력을 요구하는 이유, 그리고 IP·계정·URL의 Top N을 뽑아 탐지 임계치의 근거 를 만드는 방법을 다룬다.