What Kind of File Is This? file, stat & Reading Metadata
- Explain that on Linux a file's type comes from its CONTENT, not its name or extension
- Use file to identify what something actually is (text, image, ELF binary, shell script) by reading its magic bytes
- Read a file's metadata with stat — size, inode, permissions, owner, and the atime/mtime/ctime timestamps — and see that ls -l is a summary of it
- Combine file with command -v to find a command's binary and identify what it is
The extension is a hint, not the truth
Break the Windows habit: on Linux the name doesn't decide the type — the content does.
Coming from other systems, it's natural to assume a file is its extension — that
report.pdfis a PDF because it ends in.pdf. On Linux, that assumption is wrong, and unlearning it is the whole point of this lesson.A filename on Linux is just a name. The
.pdf,.txt,.jpgon the end is a convention for humans, not a fact the system enforces. You can rename a photo tophoto.txtand it's still a JPEG; you can name a shell scriptdatawith no extension and it still runs. Nothing about the name changes what's inside the file.So how do you know what a file really is? You look at its content. Most file formats begin with a distinctive few bytes — a magic number — that identify the format: a JPEG starts with certain bytes, a PDF starts with
%PDF, a Linux executable (ELF) starts with the bytes\x7fELF. The tool `file` reads those opening bytes and tells you what the content actually is, regardless of the name. That's the reliable answer to "what kind of file is this?" — and it's whyfileis one of the first tools you reach for with anything unfamiliar.Check yourself
On Linux, what actually determines a file's type — its name/extension, or its content?
file: identify by content
Use file to name what something really is, whatever it's called.
`file NAME` examines a file's content and prints a short description of what it is. It doesn't care about the extension — it reads the actual bytes and matches them against a database of known formats (the magic database). A few examples of what it can tell apart:
- Plain text —
ASCII text, often with the encoding. - A shell script —
Bourne-Again shell script, ASCII text executable. - A compiled program —
ELF 64-bit LSB executable, x86-64(ELF is Linux's executable format). - An image —
JPEG image dataorPNG image data, often with dimensions. - An archive —
gzip compressed dataorPOSIX tar archive.
Because it reads content,
filesees through a misleading name instantly: point it at a JPEG callednotes.txtand it still saysJPEG image data. You can pass it many files at once, andfile -reads from standard input so you can pipe data into it.file /bin/ls /etc/hostname/bin/ls: ELF 64-bit LSB pie executable, x86-64, dynamically linked /etc/hostname: ASCII text
Two files, two very different truths. /bin/ls is an ELF executable — a compiled program the CPU runs. /etc/hostname is plain ASCII text. file read the opening bytes of each and named the format; neither has an extension, and it didn't need one. printf '#!/bin/bash\necho hi\n' > myscript.txt && file myscript.txtmyscript.txt: Bourne-Again shell script, ASCII text executable
The file is named .txt, but file reads its content — the #!/bin/bash first line (a 'shebang') — and correctly calls it a shell script, not a plain text file. The name said one thing; the bytes said another, and file believes the bytes. - Plain text —
stat: the full metadata record
Read the metadata behind every file — and see that ls -l is a summary of it.
A file is more than its content. The filesystem also keeps a record of metadata about it — and `stat NAME` prints that whole record. It includes:
- Size in bytes, and how many disk blocks it occupies.
- The inode number — the filesystem's internal ID for the file (the name is just a label pointing at an inode).
- Permissions (in both
rwxand octal form), plus the owner and group. - Three timestamps, which trip people up until you name them:
- atime — last access (read)
- mtime — last modification of the contents
- ctime — last change to the metadata (permissions, owner, name) — not "creation"
Here's the connection worth seeing: `ls -l` is just a friendly summary of this same metadata. The type character, the permission bits, the owner, the size, the date —
ls -lpulls them from the file's metadata record, andstatshows you the full record they came from. So when you want the complete picture (the inode, all three timestamps, exact byte size),statis the tool;ls -lis the quick glance.Example — not runnable on your lab box
stat myscript.txtFile: myscript.txt Size: 20 Blocks: 8 IO Block: 4096 regular file Device: 801h/2049d Inode: 131074 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 1000/ ada) Gid: ( 1000/ ada) Access: 2026-07-25 10:00:00 Modify: 2026-07-25 10:00:00 Change: 2026-07-25 10:00:00
The full metadata record: size in bytes and blocks, the file type, the inode number, permissions in octal (0644) and rwx form, owner/group, and the three timestamps. ls -l shows a subset of exactly these fields — stat is the complete version. Your inode and times will differ. Example — not runnable on your lab box
stat -c '%n is %s bytes, perms %a, owner %U' myscript.txtmyscript.txt is 20 bytes, perms 644, owner ada
stat -c takes a format string so you can pull out just the fields you want: %n name, %s size, %a octal permissions, %U owner. This is how scripts read one specific piece of metadata instead of parsing the full output. Check yourself
Which stat timestamp changes when you edit a file's CONTENTS — atime, mtime, or ctime?
Is it a command — and what kind?
Combine command lookup with file to identify the program behind a name.
There's a related question: when you type a name, what runs? As the How the Shell Finds a Command lesson covered, `type NAME` (and
command -v NAME) tells you whether a name is a shell builtin, an alias, a function, or an external program on yourPATH— and for a program, which file it resolves to.That pairs naturally with
file.command -v lsgives you the path to thelsprogram; feeding that path tofiletells you what kind of file that program is. Together they answer the full question "what is this command, really?":- `type ls` — is it a builtin, alias, or a program? (and where)
- `file $(command -v ls)` — and if it's a program, what kind of file is it (a compiled ELF binary? a script?)
This is a small but powerful combination: one tool resolves the name to a file, the other identifies the file. It's how you tell whether
pythonon your system is a real binary or a wrapper script, or confirm that a command is the builtin you expected rather than a stray program on yourPATH.type cd; echo '---'; file "$(command -v ls)"cd is a shell builtin --- /usr/bin/ls: ELF 64-bit LSB pie executable, x86-64, dynamically linked
type reports that cd is a shell builtin — there's no program file for it, it's built into the shell. For ls, command -v resolves the name to /usr/bin/ls, and file identifies that as a compiled ELF executable. Name → file → type, in one line. Now do it — see through the names
Prove that content, not the name, defines the file — with file and stat.
One continuous experiment: make files whose names lie, then use
fileto get the truth andstatto read their metadata — the everyday 'what is this?' workflow.printf 'plain words here\n' > report.pdf && printf '\x89PNG\r\n\x1a\n' > notes.txt && file report.pdf notes.txtreport.pdf: ASCII text notes.txt: PNG image data
Two files with deliberately misleading names: report.pdf holds plain text, and notes.txt begins with the PNG magic bytes. file ignores the names entirely and reports what the CONTENT is — text and a PNG — exactly backwards from what the extensions claim. stat -c 'type=%F size=%s perms=%a name=%n' report.pdf notes.txttype=regular file size=16 perms=644 name=report.pdf type=regular file size=7 perms=644 name=notes.txt
stat -c pulls a few metadata fields for each: both are ordinary regular files, with their real byte sizes and permissions. Metadata describes the file as an object on disk; file (previous step) describes what its bytes mean. Two complementary views. cp report.pdf report.copy && stat -c '%n inode=%i' report.pdf report.copy; echo '--- cleaning up ---'; rm report.pdf notes.txt myscript.txt report.copy; echo donereport.pdf inode=131075 report.copy inode=131078 --- cleaning up --- done
A copy gets its OWN inode (a distinct filesystem object), which stat's %i field reveals — proof that the name is just a label and the inode is the real identity. Then we clean up. You've now seen content (file), metadata (stat), and identity (the inode) — three answers to 'what kind of file is this?'
- On Linux a file's type comes from its CONTENT, not its name — the extension is a human convention the system doesn't enforce, so renaming a file never changes what's inside it.
- file NAME identifies a file by reading its opening bytes (the magic number): text, shell script, ELF executable, image, archive — even through a misleading name or no extension.
- stat NAME prints the full metadata record: size, blocks, inode, permissions (octal + rwx), owner/group, and the three timestamps — atime (accessed), mtime (contents modified), ctime (metadata changed, NOT creation). ls -l is a summary of this.
- type / command -v answer 'what does this NAME mean and where does it resolve'; file answers 'what kind of file is this'. Chain them — file $(command -v NAME) — to identify the program behind a command.
| You write | It tells you |
|---|---|
| file NAME | what the content actually is (by magic bytes) |
| file - | identify data piped in on stdin |
| stat NAME | full metadata: size, inode, perms, owner, times |
| stat -c '%s %a %U' NAME | just the fields you ask for (size, perms, owner) |
| mtime / atime / ctime | contents changed / last read / metadata changed |
| file $(command -v NAME) | identify the program a command name resolves to |
