Signal9Signal9 — home
online
all lessons

What Kind of File Is This? file, stat & Reading Metadata

Beginner22 min5 steps
What you'll learn
  • 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
  1. 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.pdf is 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, .jpg on the end is a convention for humans, not a fact the system enforces. You can rename a photo to photo.txt and it's still a JPEG; you can name a shell script data with 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 why file is 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?

  2. 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 data or PNG image data, often with dimensions.
    • An archive — gzip compressed data or POSIX tar archive.

    Because it reads content, file sees through a misleading name instantly: point it at a JPEG called notes.txt and it still says JPEG image data. You can pass it many files at once, and file - 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.txt
    myscript.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.
  3. 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 rwx and 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 -l pulls them from the file's metadata record, and stat shows you the full record they came from. So when you want the complete picture (the inode, all three timestamps, exact byte size), stat is the tool; ls -l is the quick glance.

    Example — not runnable on your lab box

    stat myscript.txt
      File: 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.txt
    myscript.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?

  4. 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 your PATH — and for a program, which file it resolves to.

    That pairs naturally with file. command -v ls gives you the path to the ls program; feeding that path to file tells 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 python on 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 your PATH.

    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.
  5. 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 file to get the truth and stat to 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.txt
    report.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.txt
    type=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 done
    report.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?'
Recap
  • 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.
Identifying files at a glance
You writeIt tells you
file NAMEwhat the content actually is (by magic bytes)
file -identify data piped in on stdin
stat NAMEfull metadata: size, inode, perms, owner, times
stat -c '%s %a %U' NAMEjust the fields you ask for (size, perms, owner)
mtime / atime / ctimecontents changed / last read / metadata changed
file $(command -v NAME)identify the program a command name resolves to