stat

Read a file's size, mode, owner and timestamps

Updated 2026-09-07

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.
51 outputs, collapsed by default

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

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

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.

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