stat
Read a file's size, mode, owner and timestamps
stat prints what the filesystem records about a name: size, mode, owner, link count, the three
timestamps, and the inode number that holds them all. None of the contents are read, so it answers
as quickly for a hundred-gigabyte image as for an empty file.
Run on its own it prints a formatted block of every field at once. -c replaces that block with a
format string of your own, which is how stat usually appears in a script: stat -c %s report.csv
is the size and nothing else, ready to go straight into a variable. ls shows a
subset of the same fields, with its own spacing and its own rules about when to abbreviate a date,
so it is the better one to read and the worse one to parse.
The three timestamps are separate things and get confused for each other. Access is when the
contents were last read, modification is when they were last written, and change is when the inode
was last altered, so a rename or a chmod moves the change time while leaving the modification
time alone. touch can set the first two, and nothing can set the third.
One default is the reverse of what most commands do: given a symlink, stat describes the link
itself rather than the file at the end of it. -L follows it.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
site the tree the cp, mv and touch pages use as well; every mode, owner and timestamp in it is set deliberately, because stat prints them back exactly as the filesystem holds them
site:
total 16
drwxr-xr-x 2 user user 4096 Jun 15 10:00 assets
drwxr-xr-x 2 user user 4096 Jun 15 10:00 backups
-rw-r--r-- 1 user user 36 Jun 1 09:00 index.html
lrwxrwxrwx 1 user user 9 Jun 18 09:00 latest.css -> style.css
-rw-r--r-- 1 user user 33 Jun 20 09:00 style.css
site/assets:
total 4
-rw-r--r-- 1 user user 35 Jun 10 09:00 logo.svg
site/backups:
total 0
notes.txt one line, beside site/ rather than inside it
Rebuild before deploying.
Reading a file's record
Given nothing but a name, stat prints every field the inode holds. -c cuts that down to the fields you asked for, in the order you asked for them.
Read everything about a file
stat site/index.html
The whole record. Size is in bytes, Access: appears twice because the first one is the mode and the second is a time, and the last three lines are the timestamps.
Show output
Your output will differ: the device and inode numbers identify this filesystem and this file, and the change and birth times are when the example tree was built
File: site/index.html
Size: 36 Blocks: 8 IO Block: 4096 regular file
Device: 0,53 Inode: 460496 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/ user) Gid: ( 1000/ user)
Access: 2026-06-01 09:00:00.000000000 +0000
Modify: 2026-06-01 09:00:00.000000000 +0000
Change: 2026-09-07 10:49:31.215599011 +0000
Birth: 2026-09-07 10:49:31.207599011 +0000
Pick the fields you want
stat -c "%n %F %s" site/index.html site site/latest.css
-c (--format) takes a format string. %n is the name, %F the kind of thing it is and %s the size, and anything else in the string is printed as it stands.
Show output
site/index.html regular file 36
site directory 4096
site/latest.css symbolic link 9
Print one field on its own
stat -c "%s" site/style.css
No label, no padding, nothing to strip afterwards. This is the form that goes into a variable.
Show output
33
Read the same field from several files
stat -c "%s %n" site/index.html site/style.css site/assets/logo.svg notes.txt
Every argument gets the format applied to it, one line each, in the order given.
Show output
36 site/index.html
33 site/style.css
35 site/assets/logo.svg
26 notes.txt
Get a size without opening the file
stat -c %s site/index.html; wc -c < site/index.html
The answer is 36 either way. wc reads the file through to count the bytes, whereas stat asks the filesystem how many there are, so its answer costs the same whatever the size.
Show output
36
36
Ask about a name that is not there
stat site/missing.css; echo "exit $?"
The message says statx rather than stat, after the system call that current coreutils uses. Exit status 1 is what a script should test.
Show output
stat: cannot statx 'site/missing.css': No such file or directory
exit 1
Test whether a path exists at all
stat -c "%s" site/missing.css 2>/dev/null; echo "exit $?"
Discarding stderr leaves the exit status as the whole answer. [ -e path ] is shorter for this alone, and stat wins when the same call has to bring back a field as well.
Show output
exit 1
Read every field as one line
stat -t site/index.html
-t (--terse) prints the fields in a fixed order with single spaces between them, for something else to split. Name, size, blocks, mode in hex, uid, gid, then the device in hex, the inode and the link count, then the timestamps as epoch seconds.
Show output
Your output will differ: the inode number, the device number and the last two timestamps belong to this machine and this run
site/index.html 36 8 81a4 1000 1000 35 460496 1 0 0 1780304400 1780304400 1788778171 1788778171 4096
Mode and ownership
The permission bits and the owner, in whichever notation suits what you are doing with them. ls -l prints the same information already, but only in the letters.
Read the mode as digits
stat -c "%a %n" site/index.html site site/latest.css
%a is the number you would pass to chmod. A symlink is always 777 and the bits are never consulted, so the permissions that decide anything are the target's.
Show output
644 site/index.html
755 site
777 site/latest.css
Read the mode as ls draws it
stat -c "%A %n" site/index.html site
%A is the ten-character form, with the leading character giving the file type.
Show output
-rw-r--r-- site/index.html
drwxr-xr-x site
Read the owner and group by name
stat -c "%U %G %n" site/index.html
%U and %G look the numbers up in /etc/passwd and /etc/group. An account that has since been deleted has nothing to find, and prints UNKNOWN.
Show output
user user site/index.html
Read the owner and group as numbers
stat -c "%u %g %n" site/index.html
The lowercase pair is what the filesystem actually stores. Comparing files across a backup or a container wants these, since the same name can be a different number on the other machine.
Show output
1000 1000 site/index.html
Compare with what ls -l prints
stat -c "%a %A %U %G %s %n" site/index.html; ls -l site/index.html
The same fields, minus the date and the link count. ls puts them in an arrangement of its own choosing, whereas stat puts them in yours, so anything reading the line downstream can be told where to look.
Show output
644 -rw-r--r-- user user 36 site/index.html
-rw-r--r-- 1 user user 36 Jun 1 09:00 site/index.html
Check a mode after changing it
chmod 600 site/style.css; stat -c "%a %n" site/style.css
chmod prints nothing on success, so reading the mode back is how a script confirms the change landed.
Show output
600 site/style.css
Sort a directory's files by mode
stat -c "%a %n" site/index.html site/style.css site/assets/logo.svg | sort
Putting the mode first makes the output sortable, which is the usual reason for choosing an order in a format string.
Show output
644 site/assets/logo.svg
644 site/index.html
644 site/style.css
Count the names a file has
stat -c "%h %n" site site/assets site/index.html
%h is the link count. A directory starts at 2, its own entry and the . inside it, and gains one for every subdirectory's .., so site reaching 4 says it holds two of them.
Show output
4 site
2 site/assets
1 site/index.html
Give a file a second name and count again
ln site/index.html site/mirror.html; stat -c "%h %n" site/index.html site/mirror.html
A hard link is not a copy. Both names now report a count of two because there is one file with two directory entries pointing at it.
Show output
2 site/index.html
2 site/mirror.html
Prove two names are the same file
ln site/index.html site/mirror.html; [ "$(stat -c %i site/index.html)" = "$(stat -c %i site/mirror.html)" ] && echo "one file, two names"
%i is the inode number, unique within a filesystem. Comparing sizes or checksums would say the same thing about two files that happen to match; comparing inodes says there is only one.
Show output
one file, two names
Look at something behind a directory you cannot enter
mkdir -p locked/inner; touch locked/inner/secret.txt; chmod 000 locked; stat locked/inner/secret.txt; chmod 755 locked
Reading a file's record needs search permission on every directory in the path, and locked has none. The final chmod puts it back so the rest of the page is unaffected.
Show output
stat: cannot statx 'locked/inner/secret.txt': Permission denied
The three timestamps
Access is the last read, modification the last write, and change the last alteration to the inode itself. %x, %y and %z print them; %X, %Y and %Z give the same instants as epoch seconds.
Read the modification time
stat -c "%y %n" site/index.html
Nanoseconds and the UTC offset come as standard. Most filesystems record whatever precision they keep, and this one stores zeroes because touch -d set the time to a whole second.
Show output
2026-06-01 09:00:00.000000000 +0000 site/index.html
Cut the fraction and the offset off
stat -c "%.19y %n" site/index.html site/style.css
%.19y keeps the first 19 characters, which is the date and the time to the second. Any width works, so %.10y gives the date alone.
Show output
2026-06-01 09:00:00 site/index.html
2026-06-20 09:00:00 site/style.css
Read the access and modification times together
stat -c "%.19x accessed, %.19y modified" site/style.css
Anything outside the specifiers is printed literally, so a format string can label its own fields.
Show output
2026-06-20 09:00:00 accessed, 2026-06-20 09:00:00 modified
Read a time as seconds since the epoch
stat -c "%Y %n" site/index.html site/style.css
An integer sorts and subtracts. Every date format stat can print is awkward to compare in a shell, and these are just numbers.
Show output
1780304400 site/index.html
1781946000 site/style.css
Turn an epoch time back into a date
date -d @$(stat -c %Y site/index.html) "+%A %-d %B %Y"
date -d @seconds reads the number back, and its own format string decides how it looks.
Show output
Monday 1 June 2026
Work out how old a file is
days=$(( ($(date +%s) - $(stat -c %Y site/index.html)) / 86400 )); echo "index.html was last written $days days ago"
Subtracting one epoch time from another gives seconds, and 86400 of those make a day. Integer division truncates, so this is a floor rather than a rounding.
Show output
Your output will differ: the number grows by one every day
index.html was last written 98 days ago
Find the newest of a set of files
stat -c "%Y %n" site/*.css site/*.html | sort -rn | head -1
Epoch seconds first, sorted numerically in reverse. ls -t answers the same question for a directory, and this version works on any list of names you can build.
Show output
1781946000 site/style.css
Watch a chmod move the change time
before=$(stat -c %Z site/index.html); sleep 1; chmod 600 site/index.html; after=$(stat -c %Z site/index.html); echo "modified $(stat -c %.19y site/index.html)"; [ "$after" -gt "$before" ] && echo "change time moved"
The contents were never touched, so the modification time stands still while the change time follows the chmod. %Z gives each reading as a number, which is what makes them comparable in a shell. A backup tool that compares modification times alone misses permission changes for this reason.
Show output
modified 2026-06-01 09:00:00
change time moved
See when a file was created
touch fresh.txt; touch -d "2020-01-01 12:00:00" fresh.txt; stat -c "%.19w born, %.19y modified" fresh.txt
%w is the birth time, recorded when the inode was made. touch moved the modification time six years into the past and left the birth time where it was, and there is no flag anywhere that moves it.
Show output
Your output will differ: the birth time is when the example ran
2026-09-07 10:50:58 born, 2020-01-01 12:00:00 modified
Symlinks, and which end you are asking about
stat describes the link rather than its target, which is the opposite of what most commands do with a symlink. -L (--dereference) switches it round.
Show a link and its target
stat -c "%N" site/latest.css
%N quotes the name and, for a symlink, adds where it points. Quoting is what makes it safe to read back when a name contains a space or a newline.
Show output
'site/latest.css' -> 'style.css'
Read the link rather than the file
stat -c "%F %s %n" site/latest.css
The size is the length of the string style.css. A symlink is a small file whose contents are a path.
Show output
symbolic link 9 site/latest.css
Follow the link instead
stat -L -c "%F %s %n" site/latest.css
The same name now reports on style.css, at 33 bytes. %n still prints the name you gave.
Show output
regular file 33 site/latest.css
Read the target's timestamp
stat --dereference -c "%.19y %n" site/latest.css
The 20 June here belongs to style.css; the link itself carries the 18th. Any script comparing timestamps through a symlink has to say which of the two it means.
Show output
2026-06-20 09:00:00 site/latest.css
Describe a link whose target is gone
ln -s gone.css site/broken.css; stat -c "%N %F" site/broken.css
The link is a perfectly good file and stat reads it without complaint. Nothing checks that a symlink's target exists, at the moment it is made or ever after.
Show output
'site/broken.css' -> 'gone.css' symbolic link
Try to follow a broken link
ln -s gone.css site/broken.css; stat -L site/broken.css; echo "exit $?"
-L asks about the target, and there is no target. Running the same name with and without -L is how a script tells a dangling link from a missing one.
Show output
stat: cannot statx 'site/broken.css': No such file or directory
exit 1
Size against space used
%s is how long the file is. What it occupies is %b blocks of %B bytes, and the two answers often disagree.
See the size and the blocks together
stat -c "%s %b %B %n" site/index.html
36 bytes stored in 8 blocks of 512, which is 4096: one filesystem block, the smallest unit it will hand out. This is why a directory of tiny files takes far more room than adding up their sizes suggests.
Show output
36 8 512 site/index.html
Make a sparse file and measure it
truncate -s 1M sparse.img; stat -c "%s %b %n" sparse.img
A megabyte long and zero blocks used. truncate declared a length without writing anything, so the filesystem recorded the length and allocated nothing.
Show output
1048576 0 sparse.img
Compare what du says about it
truncate -s 1M sparse.img; du -h sparse.img; stat -c "%s" sparse.img
du reports space consumed and stat -c %s reports length, so the two disagree by the whole megabyte. Neither is wrong; they answer different questions.
Show output
0 sparse.img
1048576
Read a directory's size
stat -c "%s %n" site site/backups
The size of a directory is the space its list of names takes up, and an empty one is charged the same 4096 as a full one here. It says nothing about what is inside.
Show output
4096 site
4096 site/backups
Read a file that reports no size
stat -c "%s %F %n" /proc/cpuinfo
Zero bytes, and regular empty file, for something cat will happily print pages from. Files under /proc are generated when read, so there is nothing on a disk for stat to measure.
Show output
0 regular empty file /proc/cpuinfo
Ask what kind of thing a name is
mkfifo pipe.tmp; stat -c "%F %s %n" pipe.tmp
%F names the seven types Unix has. A fifo is a named pipe, and it has no size because nothing is stored in it.
Show output
fifo 0 pipe.tmp
Look at a device node
stat -c "%F %n" /dev/null /dev/zero
A character special file is a name that a driver answers to rather than a file with contents. %t and %T give the major and minor numbers that say which driver.
Show output
character special file /dev/null
character special file /dev/zero
The filesystem underneath
-f (--file-system) changes the subject from the file to the filesystem holding it, and the format specifiers change with it.
Name the filesystem a path is on
stat -f -c "%T %n" /dev/shm /proc
Neither of these is on a disk. /dev/shm is memory and /proc is the kernel answering questions, and a path on your root filesystem would report ext2/ext3 for ext4.
Show output
tmpfs /dev/shm
proc /proc
Read the block size
stat -f -c "%s %S %n" site
Under -f, %s is the block size the filesystem prefers for transfers and %S is the one it allocates in. A specifier means something different here from what it means without the flag.
Show output
4096 4096 site
Find the mount point a file sits on
stat -c "%m %n" site/index.html
%m walks up until the filesystem changes. A file under /home on its own partition would answer /home here rather than /.
Show output
/ site/index.html
In a script
The reason to prefer stat over parsing ls is that you choose the fields and the separator, so nothing downstream has to guess at columns.
Put a size into a variable
size=$(stat -c %s site/index.html); echo "index.html is $size bytes"
The field comes back with no whitespace around it, so nothing has to be trimmed before it is used.
Show output
index.html is 36 bytes
Guard on a file having contents
if [ "$(stat -c %s notes.txt)" -gt 0 ]; then echo "notes.txt has something in it"; fi
[ -s notes.txt ] is the shorter way to ask this exact question. The stat form earns its length when the threshold is a real size rather than zero.
Show output
notes.txt has something in it
Print one line per file
stat --printf="%n %s\n" site/index.html site/style.css
--printf interprets backslash escapes and adds no newline of its own, so \n at the end is doing the line breaks. -c is the same thing with the newline supplied.
Show output
site/index.html 36
site/style.css 33
Print with no trailing newline
stat --printf="%s" site/index.html; echo "|"
The | lands on the same line, which is what makes this form safe inside a larger string being built up.
Show output
36|
Write a sentence around the fields
stat --format="%n is %s bytes, mode %a" site/style.css
--format is the long spelling of -c. Text around the specifiers goes through untouched, so the output can be the finished message.
Show output
site/style.css is 33 bytes, mode 644
Handle a name with a space in it
touch "my report.txt"; stat -c "%N %s" "my report.txt"
%N quotes the name, so a reader of the output can tell where it ends. %n would print it bare and leave the space looking like a separator.
Show output
'my report.txt' 0
Describe everything in a directory
stat -c "%n %F" site/*
The shell expands the glob and sorts it, and stat takes as many names as it is given. Dotfiles are missing because the glob skips them, not because stat does.
Show output
site/assets directory
site/backups directory
site/index.html regular file
site/latest.css symbolic link
site/style.css regular file