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.
The route
Jump straight to the step you need, or tick off Done means at the end.
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.
3. Inspect a symbolic link without following it
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
statimplementation 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
-fchanges the meaning of the format sequences to filesystem data. - You diagnosed path and permission errors without changing files or service configuration.