Years ago I inherited a directory full of files with no extensions — a legacy export process had stripped them all somewhere along the way, and I had no idea which ones were images, which were compressed archives, and which were plain config text. Renaming them by guesswork felt like a bad idea. file sorted the whole mess out in seconds, correctly identifying JPEGs, gzip archives, and plain text files despite none of them having a usable extension. That’s the moment I understood what file actually does differently from just looking at a filename.
What file Does
file examines the actual content of a file — not its name or extension — and reports what type of data it contains.
file [OPTIONS] FILE...
I tested this against a plain text file and a compiled binary:
$ file sample.txt
sample.txt: ASCII text
$ file /bin/ls
/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=daa5130f2a41f7fbc8662048f3294f3d439ca7ff, for GNU/Linux 3.2.0, stripped
Notice how much detail file extracts from the binary — not just “this is an executable,” but the exact architecture, byte order, linking type, and even the dynamic linker path, all pulled from the file’s actual binary structure.
Why This Matters: Extensions Lie, Content Doesn’t
On Linux, a file extension is just a naming convention — nothing enforces that a .txt file actually contains text, or that a .jpg file is actually a JPEG. Malware sometimes deliberately uses misleading extensions. Files can be renamed, corrupted, or exported without extensions entirely (as in my anecdote above). file sidesteps all of that by inspecting the actual bytes of the file and matching them against known signatures, structural patterns, and heuristics — this is fundamentally more trustworthy than trusting a filename.
How file Works Internally
file primarily relies on magic numbers — specific byte sequences at known offsets within a file that reliably identify its format. For example, gzip files start with the bytes 1F 8B, PNG images start with a specific 8-byte signature, and ELF binaries (Linux executables) start with the bytes 7F 45 4C 46 (“\x7fELF”). These signatures and their associated descriptions are defined in a magic database, traditionally stored at /usr/share/file/magic or compiled into a binary magic.mgc file.
file‘s detection logic works in three layered stages:
- Filesystem tests — checks things like whether the path is a directory, a symlink, a socket, a named pipe, or otherwise not a regular file, which can often be determined without reading file content at all.
- Magic number tests — the core of
file‘s identification, matching byte patterns against the magic database. - Language/text heuristics — if no magic number matches,
filefalls back on textual heuristics (checking for valid UTF-8, common patterns of markup, shebang lines like#!/bin/bash, and so on) to guess whether a file is source code, plain text, or genuinely binary/unknown data.
I confirmed the version in this environment reports itself as file-5.45, and its magic database is what gives it such broad, accurate coverage across thousands of file formats without needing per-format hardcoded logic beyond the magic pattern definitions themselves.
Core Options and Parameters
-i / --mime — Output MIME Type Instead of a Human-Readable Description
$ file -i sample.txt
sample.txt: text/plain; charset=us-ascii
$ file -i /bin/ls
/bin/ls: application/x-pie-executable; charset=binary
I tested this and confirmed it produces standard MIME type strings (text/plain; charset=us-ascii) instead of the default prose description — extremely useful when the output needs to be parsed programmatically or matched against expected content types, for instance in an upload validation script.
--mime-type — MIME Type Only, Without Charset
$ file --mime-type sample.txt
sample.txt: text/plain
A more minimal variant of -i that omits the charset portion, giving you just the bare MIME type string.
-b / --brief — Suppress the Filename in Output
$ file -b sample.txt
ASCII text
Useful when scripting against a single file where you already know the filename and just want the type description, without needing to strip the filename: prefix yourself.
-z — Look Inside Compressed Files
By default, file reports a gzip/compressed file simply as compressed data. With -z, it also inspects the content inside the compression, reporting what the decompressed data actually is:
$ file sample.txt.gz
sample.txt.gz: gzip compressed data, was "sample.txt", last modified: Fri Jul 31 01:36:51 2026, from Unix, original size modulo 2^32 36
$ file -z sample.txt.gz
sample.txt.gz: ASCII text (gzip compressed data, was "sample.txt", last modified: Fri Jul 31 01:36:51 2026, from Unix)
I tested this directly: without -z, file correctly identifies the gzip wrapper and its metadata (original filename, modification time). With -z, it goes a step further and reports that the decompressed payload is ASCII text, appended alongside the compression details — genuinely useful when you have a directory of .gz files and want to know what’s actually inside them without manually decompressing each one.
-L — Follow Symbolic Links
By default, file reports on the symlink itself rather than the file it points to:
$ file symlink_test
symlink_test: symbolic link to sample.txt
With -L, file follows the link and reports on the target’s actual content type instead:
$ file -L symlink_test
symlink_test: ASCII text
I tested both and confirmed the difference clearly — without -L, you learn it’s a symlink and where it points; with -L, you learn what kind of file the symlink ultimately resolves to.
Handling Directories, Empty Files, and Special Files
file correctly identifies non-regular-file cases without needing content inspection:
$ file /home/claude
/home/claude: directory
$ touch empty.txt && file empty.txt
empty.txt: empty
I confirmed both of these directly — directories are reported immediately as directory, and zero-byte files are reported as empty, both via filesystem-level tests rather than magic-number matching.
Checking Multiple Files at Once
$ file sample.txt nums.txt /bin/ls
sample.txt: ASCII text
nums.txt: ASCII text
/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=daa5130f2a41f7fbc8662048f3294f3d439ca7ff, for GNU/Linux 3.2.0, stripped
I confirmed file accepts multiple filenames in a single invocation and neatly column-aligns the output, which is both more readable and considerably faster than invoking file separately per file in a loop.
-f LISTFILE — Read Filenames from a List File
find /data -type f > /tmp/filelist.txt
file -f /tmp/filelist.txt
Rather than passing filenames as shell arguments (which can hit command-line length limits on very large directory trees), -f reads the list of files to check from a text file, one path per line — a much more scalable approach for scanning huge numbers of files.
-k — Continue Checking After First Match
By default, file stops at the first matching magic rule. -k (keep going) continues checking additional rules and reports every match found, which can surface additional structural information about ambiguous or multi-format files.
-s — Read Special/Device Files
Normally file avoids reading the actual content of block or character special device files (like /dev/sda) for safety and practicality reasons. -s forces file to attempt reading them anyway — useful for identifying the filesystem type present on a raw disk device, but should be used carefully and typically requires appropriate permissions.
Practical, Real-World Examples
1. Identifying Unknown Files in an Inherited Directory
file *
2. Validating Uploaded File Types in a Script
#!/bin/bash
TYPE=$(file --mime-type -b "$1")
if [[ "$TYPE" != "image/jpeg" && "$TYPE" != "image/png" ]]; then
echo "Rejected: unsupported file type ($TYPE)" >&2
exit 1
fi
This checks the actual content type of an uploaded file rather than trusting its extension — a meaningfully more secure validation approach than checking *.jpg against the filename alone, though real-world upload validation should still combine this with other checks (size limits, actual image parsing, etc.) rather than relying on file alone.
3. Auditing a Directory of Compressed Files
for f in *.gz; do
file -z "$f"
done
4. Finding All Executable Binaries in a Directory Tree
find /opt/myapp -type f -exec file {} \; | grep ELF
5. Bulk-Identifying Files from a Generated List
find /mnt/recovered_data -type f > /tmp/recovered_files.txt
file -f /tmp/recovered_files.txt > /tmp/identified_types.txt
This is a genuinely common data-recovery workflow — after recovering files from a damaged filesystem where original names and extensions may be lost or scrambled, file is often the first tool used to triage what was actually recovered.
file in Shell Scripting and Automation
A pattern I use in backup verification scripts, confirming that backup archives are actually valid compressed archives before considering a backup job successful:
#!/bin/bash
BACKUP="/backups/daily_$(date +%Y%m%d).tar.gz"
TYPE=$(file -b "$BACKUP")
if [[ "$TYPE" == *"gzip compressed data"* ]]; then
echo "Backup verified: $BACKUP"
else
echo "WARNING: backup file does not appear to be valid gzip data: $TYPE" >&2
exit 1
fi
This catches a class of failure that a simple “does the file exist and have nonzero size” check would miss — a backup job that produced a truncated or corrupted archive due to a mid-write failure.
Comparing file to Related Commands
| Task | Best Tool |
|---|---|
| Identifying actual file content type | file |
| Checking a file’s extension only | (no dedicated tool — basename/parameter expansion) |
| Inspecting binary structure in depth (ELF headers, sections) | readelf, objdump |
| Viewing raw hex/byte content | xxd, od -c, hexdump |
| Checking MIME type via a library rather than CLI | libmagic (the library file itself uses) |
file and tools like readelf are complementary rather than competing — file gives you a fast, high-level “what kind of thing is this” answer covering thousands of formats generically, while readelf/objdump give you deep, format-specific structural detail once you already know (often via file) that you’re looking at an ELF binary.
Troubleshooting Common file Issues
file reports “data” with no useful description — this means no magic pattern matched and the content didn’t look like recognizable text either; this happens with proprietary, encrypted, or otherwise unusual binary formats not covered by the magic database, or with genuinely corrupted files.
Magic database seems out of date, missing newer formats — the magic database is versioned separately from file itself in some distributions; updating the file package (or the file-magic/shared-mime-info package depending on distribution) can add support for newer or less common formats.
Symlink reports itself instead of the target — this is default, intentional behavior; add -L if you want file to follow the link and report on the target’s content instead.
MIME type output doesn’t match what a web server or upload handler expects — remember file‘s MIME detection is content-based heuristic guessing, not a formal certification; for strict validation needs, cross-reference with format-specific parsing libraries rather than relying on file‘s MIME guess alone for security-critical decisions.
Performance Optimization
file typically only needs to read a small portion of a file’s beginning (and sometimes end, for certain formats) to make its determination, rather than reading the entire file — this makes it fast even on very large files. For scanning huge numbers of files, using -f with a pre-generated file list (rather than invoking file once per file in a shell loop) avoids the repeated process-startup overhead of spawning a new file process for every single file.
Security Implications
file is commonly used as a first line of defense in upload validation and forensic triage specifically because it inspects actual content rather than trusting potentially attacker-controlled filenames or extensions. That said, file‘s detection is heuristic and signature-based, not a full format-correctness validator — a maliciously crafted file can sometimes be built to satisfy a magic number check while still being harmful when actually processed by the target application (a classic example being polyglot files valid as more than one format simultaneously). Treat file‘s output as a useful signal, not a complete security guarantee, and combine it with proper format-specific validation for anything security-sensitive.
Compatibility Across Distributions
file (tested here at version 5.45) is available by default on virtually every Linux distribution — Ubuntu, Debian, Fedora, RHEL/CentOS, Arch, openSUSE — since it’s a foundational utility relied upon by package managers, desktop file managers, and countless scripts. It’s also available on macOS and BSD systems, sharing largely the same magic-number-based approach, since the same underlying libmagic project (or a compatible reimplementation) underpins most modern file implementations across Unix-like systems.
Summary
file answers a deceptively important question — “what actually is this file?” — by inspecting real content rather than trusting a name or extension. Between -i/--mime-type for programmatic use, -z for peeking inside compressed archives, -L for resolving symlinks, and -f for scaling up to large batches of files, it covers the great majority of file-identification needs in everyday system administration, scripting, and even basic forensic triage.
References
fileandlibmagicProject: https://www.darwinsys.com/file/man file(local manual page)man 5 magic— documentation on the magic number database format (local manual page)
