Home / Alt manpages / stat(1)

  • stat(1)
  • User command
  • linux

Inspect File and Filesystem Metadata with stat

stat hands you a file's exact size, timestamps and permissions instead of leaving you to guess from a directory listing. This guide covers checking a file, telling a symbolic link apart from its target, reading filesystem capacity, and pulling stable fields for scripts. The examples use GNU coreutils 9.4, installed here as package version 9.4-3ubuntu6.3.

Allow about fifteen minutes. You need a shell and a readable path. The checks are normally unprivileged; reach for elevated privileges only when the path itself is inaccessible, and do not use sudo just to make an uncertain path or result look successful.

1. Check the installed command

Start by confirming which executable your shell will actually run and which version it provides:

$ command -v stat
/usr/bin/stat
$ stat --version | head -1
stat (GNU coreutils) 9.4

A shell can provide its own builtin with the same name, so the full path and version check are what make the rest of these examples reproducible. If your output names a different implementation, read that implementation's own help before copying a GNU-specific format string.

Checkpoint

Confirm the syntax available on this machine:

$ stat --help | sed -n '1,18p'
Usage: stat [OPTION]... FILE...
Display file or file system status.

  -L, --dereference     follow links
  -f, --file-system     display file system status instead of file status
  -c, --format=FORMAT   use the specified FORMAT instead of the default
      --printf=FORMAT   like --format, but interpret backslash escapes
  -t, --terse           print the information in terse form

The exact help text gains or loses lines between releases. The options used here are the ones the installed GNU command actually documents.

2. Read ordinary file metadata

Run stat with a path to see the default, human-readable report. Swap in a file you expect to exist:

$ stat /path/to/report.txt
  File: /path/to/report.txt
  Size: 6          Blocks: 8          IO Block: 4096   regular file
Device: ...        Inode: ...         Links: 1
Access: (0644/-rw-r--r--)  Uid: (...)  Gid: (...)
Access: 2026-09-27 04:20:02.004665628 +0100
Modify: 2026-09-27 04:20:02.004665628 +0100
Change: 2026-09-27 04:20:02.004665628 +0100
 Birth: 2026-09-27 04:20:02.004665628 +0100

Your device, inode, account names and times will differ. Size is the file length in bytes. Blocks is allocated storage reported in filesystem blocks, so it is not just a second spelling of the file size. Access shows permission bits and the last access time, Modify is the last data modification, and Change is the last metadata or content status change. Birth time shows as - when the filesystem does not provide it.

If the path is wrong, GNU stat exits non-zero and reports the error. Check it with ls -ld -- /path/to/report.txt rather than immediately adding sudo, which can just hide an ownership or deployment problem behind a different identity.

By default, stat reports the link itself. That matters whenever a service path, configuration file or release directory might actually be a symlink:

$ stat /path/to/current
  File: /path/to/current -> release-2026-09-27
  Size: 16         Blocks: 0          IO Block: 4096   symbolic link
Device: ...        Inode: ...         Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (...)  Gid: (...)

The reported size here is the length of the link's target text, not the size of the file it points to. Use -L or --dereference when you actually want the target's metadata instead:

$ stat -L -c 'type=%F size=%s owner=%U modified=%y' /path/to/current
type=regular file size=1048576 owner=alice modified=2026-09-27 04:20:02.004665628 +0100

Checkpoint

Decide which object your question is actually about. Use plain stat to audit the link, and stat -L to audit what it resolves to. A dangling link fails when dereferenced, even though the link itself can still be inspected fine.

4. Select fields for a script

The default report suits a human reading it, but a script should ask for only the fields it needs. GNU format sequences include %F for file type, %A for readable permissions, %a for octal permissions, %s for bytes, %U for the user name, %G for the group name, and %y for the modification time:

$ stat --printf='type=%F\nmode=%A\noctal=%a\nsize=%s\nowner=%U\ngroup=%G\nmodified=%y\n' /path/to/report.txt
type=regular file
mode=-rw-r--r--
octal=644
size=6
owner=alice
group=alice
modified=2026-09-27 04:20:02.004665628 +0100

Reach for --printf when you need backslash escapes such as \n. Unlike --format, it does not append a mandatory final newline, so add one yourself if you want tidy command-line output. A format string describes each input path, and the command prints one record per path.

For a simple gate, check the command's exit status as well as the value it returns:

$ if stat --printf='%a\n' /path/to/report.txt; then
>     echo 'metadata read successfully'
> else
>     echo 'metadata read failed' >&2
>     exit 1
> fi
644
metadata read successfully

Do not compare human-readable timestamps or owner names when a numeric identity or an exact epoch value is the real contract you care about, and quote paths that come from variables.

5. Check the filesystem behind a path

Add -f or --file-system to ask about the filesystem holding a path rather than the path itself:

$ stat -f --printf='mount=%n\ntype=%T\nblock-size=%S\nfree-for-user=%a\nfree-blocks=%f\n' /path/to/report.txt
mount=/path/to/report.txt
type=ext2/ext3
block-size=4096
free-for-user=844486806
free-blocks=893104134

Filesystem format sequences mean something different from file format sequences: here %a is free blocks available to an ordinary user, %f is free blocks in the filesystem overall, %S is the fundamental block size, and %T is the human-readable filesystem type. Do not reuse a file-oriented format string with -f and assume the values still mean what they meant before.

The mount field shown here is just the input name in GNU's format, not the actual mount point; use a mount-aware tool such as findmnt separately if you need that. A capacity check like this is read-only, but acting on a low-space result can affect services, so investigate before you delete anything.

6. Use terse output only when its contract suits you

--terse produces a compact, positional record:

$ stat --terse /path/to/report.txt
/path/to/report.txt 6 8 81a4 1004 1004 902 124257160 1 0 0 1790479202 1790479202 1790479202 1790479202 4096

Those numbers are hard to review at a glance and the record is tied to a documented but easy-to-get-wrong field order. Prefer an explicit --printf format for new scripts, especially when a later reader will need to understand each field. If you must consume terse output anyway, pin the GNU implementation and version in the script's own notes.

7. Diagnose failures safely

For "No such file or directory", check the parent directory and the spelling. For "Permission denied", identify the account and permissions before you escalate anything:

$ id
$ name='/path/to/report.txt'
$ ls -ld -- "${name%/*}" "$name"
$ test -r "$name" && echo readable
readable

If the path has no slash in it, ${name%/*} just returns the same string rather than a useful parent directory, so use an absolute or directory-qualified path while investigating. Do not change permissions, ownership or symlink targets as a diagnostic shortcut: those changes alter system state and deserve a separate, reviewed operation with a recovery plan.

Done means

  • You confirmed the GNU stat implementation and installed coreutils version.
  • You can read default file metadata and interpret size, blocks and timestamps correctly.
  • You can choose between inspecting a symlink and following it to its target.
  • Your scripts use explicit format sequences and check the command's exit status.
  • You know that -f changes the meaning of the format sequences to filesystem data.
  • You diagnosed path and permission errors without changing files or service configuration.