리눅스 stat 명령어 사용법 - 권한·inode·파일 시각 3종 정확히 읽기
이 명령어를 언제 쓰는가
파일 하나를 두고 문제가 생겼을 때 첫 단추로 친다. ls -l은 권한과 mtime만 보여 주지만, 실제로 필요한 정보는 대개 그 바깥에 있다. inode 번호가 무엇인지, 하드링크가 몇 개 걸려 있는지, 심볼릭 링크가 어디를 가리키는지, 그리고 세 가지 시각 중 어느 것이 언제 바뀌었는지다.
"배포했는데 반영이 안 된다", "로그 로테이션이 안 돈다", "권한을 줬는데 여전히 Permission denied" 같은 문의는 거의 전부 stat 한 줄로 범위가 좁혀진다. 특히 ctime은 생성 시각이 아니다라는 점만 알아도 오진이 크게 줄어든다.
기본 형식
stat [옵션] 파일...
옵션 없이 쓰면 사람이 읽는 전체 정보를 여러 줄로 출력한다. 스크립트에서는 그대로 파싱하지 말고 포맷 지정 옵션으로 필요한 필드만 한 줄로 뽑는다. 포맷 옵션은 -c(GNU/리눅스)와 -f(BSD/macOS)로 서로 다르다.
자주 쓰는 옵션
| 옵션 | 의미 | 예시 |
|---|---|---|
| -c 포맷 | GNU. 지정한 필드만 출력. 스크립트의 기본 | stat -c '%a %U %n' /etc/passwd |
| --printf | -c와 같지만 줄바꿈을 자동으로 붙이지 않는다 | stat --printf '%s' f.log |
| -L | 심볼릭 링크를 따라가 대상 파일 정보를 본다 | stat -L /usr/bin/vi |
| -f | GNU에서는 파일시스템 정보. BSD에서는 포맷 문자열 | stat -f /data |
| -t | 모든 값을 공백 구분 한 줄로. 기계 파싱용 | stat -t /etc/hosts |
자주 쓰는 GNU 포맷 지정자
| 지정자 | 의미 |
|---|---|
| %n / %N | 파일명 / 링크 대상까지 화살표로 표시한 이름 |
| %a / %A | 8진수 권한(644) / 문자열 권한(-rw-r--r--) |
| %U %G / %u %g | 소유자·그룹 이름 / UID·GID 숫자 |
| %s | 바이트 크기 |
| %i / %h / %d | inode 번호 / 하드링크 수 / 디바이스 번호 |
| %F | 파일 종류(regular file, directory, symbolic link) |
| %y %Y | mtime을 사람이 읽는 형식 / epoch 초 |
| %x %X, %z %Z | atime, ctime (각각 사람이 읽는 형식과 epoch) |
| %W | 생성 시각(birth) epoch. 지원 안 하면 0 |
실전 예제
1) 배포 후 설정 파일 권한과 소유자를 한 줄로 검증한다. 사람이 눈으로 ls -l을 읽는 것보다 비교가 쉽다.
stat -c '%a %U:%G %n' /etc/nginx/nginx.conf
644 root:root /etc/nginx/nginx.conf
2) 하트비트 파일이 몇 초 전 것인지 계산한다. 감시 스크립트에서 가장 많이 쓰는 형태다.
age=$(( $(date +%s) - $(stat -c %Y /run/app/heartbeat) ))
if [ "$age" -gt 120 ]; then echo "stale: ${age}s"; fi
3) 두 파일이 같은 실체(하드링크)인지 판정한다. 디바이스 번호와 inode가 모두 같으면 같은 파일이다.
stat -c '%d:%i %h %n' /var/log/app.log /backup/app.log
4) 심볼릭 링크 자체와 대상을 구분해서 본다. 링크가 끊겼는지 확인할 때 쓴다.
stat -c '%F %N' /etc/alternatives/java
stat -L -c '%F %s %n' /etc/alternatives/java
5) macOS·FreeBSD에서 같은 일을 한다. 포맷 문자열이 완전히 다르다.
stat -f '%Sp %Su:%Sg %z %N' /etc/hosts
-rw-r--r-- root:wheel 213 /etc/hosts
함정과 주의점
ctime을 생성 시각으로 읽으면 안 된다. ctime은 change time, 즉 inode 메타데이터가 마지막으로 바뀐 시각이다. chmod나 chown만 해도, 심지어 하드링크를 하나 더 걸어도 ctime은 갱신된다. 생성 시각은 별도의 birth time(%W, stat 출력의 Birth)이며 xfs·btrfs·ext4(inode 256바이트 이상)와 충분히 새로운 커널에서만 값이 나온다. 지원하지 않으면 - 또는 0으로 표시된다.
-f 옵션의 의미가 플랫폼마다 정반대다. 리눅스(GNU coreutils)에서 stat -f /data는 파일시스템 블록 크기와 여유 블록을 보여 주지만, macOS·FreeBSD에서 -f는 포맷 문자열을 받는 옵션이다. 맥에서 개발하고 리눅스에 배포하는 스크립트가 여기서 자주 깨진다. -t도 마찬가지여서, 리눅스에서는 인자를 받지 않는 terse 출력이지만 macOS·FreeBSD에서는 시각 포맷 문자열을 받는 옵션이다. 맥에서 stat -t /etc/hosts를 치면 /etc/hosts가 포맷 문자열로 먹히고 표준 입력을 stat 하므로, 에러도 없이 엉뚱한 결과가 나온다. 이식성이 필요하면 uname -s로 분기하라.
atime으로 "이 파일 안 쓰는 것 같다"를 판단하지 마라. 요즘 배포판은 기본 마운트 옵션이 relatime이라 atime이 mtime보다 오래됐을 때만 갱신되고, noatime으로 마운트했다면 아예 갱신되지 않는다. 실제 접근 여부는 lsof나 접근 로그로 확인해야 한다.
%s와 실제 디스크 점유는 다를 수 있다. sparse 파일이나 압축 파일시스템에서는 크기가 100GB인데 실제 블록은 몇 MB뿐일 수 있다. 실점유는 %b(블록 수)와 %B(블록 크기)를 곱해서 봐야 하며, 이 값이 du가 보고하는 숫자와 일치한다.
함께 보면 좋은 명령어
- ls — 여러 파일의 권한과 시각을 한눈에 훑을 때. 정밀 조사는 stat이 맡는다.
- find —
-mtime,-newer로 시각 조건에 맞는 파일을 골라낼 때. - file — 파일의 종류를 확장자가 아니라 내용으로 판별할 때.