The Filesystem and Navigation · about 40 min

Looking closely: ls -l, file types and stat

Why this matters

Plain ls shows names. When something goes wrong (a script cannot read a file, a disk is full, a "text" file is really binary, a config change did not take effect) you need the rest of the story: who owns the file, who may read it, how big it is, when it changed, and what kind of thing it is. This lesson teaches you to read that information at a glance, and introduces the inode, the kernel's real record of a file.

Hidden files

Open the shell for this lesson. Any name starting with a dot is hidden: ls skips it unless you ask.

$ ls survey
archive  notes.txt  report.pdf  todo.md
$ ls -a survey
.  ..  .cache.db  .hidden-note  .profile  archive  notes.txt  report.pdf  todo.md
$ ls -A survey
.cache.db  .hidden-note  .profile  archive  notes.txt  report.pdf  todo.md

There is nothing special about hidden files to the kernel; it is purely a convention of ls (and of shell wildcards, as you will see). It keeps configuration files like ~/.bashrc and ~/.profile out of the way. Run ls -A ~ to see the ones in your home directory.

The long listing

ls -l shows one line of detail per entry:

$ ls -l survey
total 20
drwxr-xr-x. 1 learner learner    14 Nov  1 14:20 archive
-rw-r--r--. 1 learner learner  1200 Nov  1 14:20 notes.txt
-rw-r--r--. 1 learner learner  5000 Nov  1 14:20 report.pdf
-rw-r--r--. 1 learner learner   300 Nov  1 14:20 todo.md

Taking one line apart:

-rw-r--r--.  1  learner  learner  1200  Nov  1 14:20  notes.txt
│└───┬───┘│  │  └──┬──┘  └──┬──┘  └┬─┘  └────┬─────┘  └───┬───┘
│    │    │  │     │        │      │         │            name
│    │    │  │     │        │      │         last modification time
│    │    │  │     │        │      size in bytes
│    │    │  │     │        group
│    │    │  │     owner (user)
│    │    │  link count (names pointing at this file; module 3)
│    │    a dot or + here marks extra security info (SELinux label, ACL)
│    permissions: owner rwx, group rwx, others rwx (module 4)
type

total 20 at the top is the disk space used by the listed files, in 1024-byte blocks.

Useful companions:

OptionEffect
-hhuman-readable sizes (4.9K, 12M) together with -l
-tsort by modification time, newest first
-Ssort by size, largest first
-rreverse the sort order
-dlist a directory itself rather than its contents
-Rrecurse into subdirectories
-ishow inode numbers
-Fappend a marker: / directory, * executable, @ symlink

They combine: ls -lhAS is "long, human sizes, almost all, by size", and ls -ltr ("newest last") is a classic for finding what changed recently in a log directory.

File types

The first character of the long listing is the type:

CharTypeExample
-regular file: any bytes (text, image, program)/etc/hostname
ddirectory/etc
lsymbolic link: a pointer to another path/bin
ccharacter device: a byte stream device/dev/null, /dev/tty
bblock device: a disk/dev/sda (not in containers)
pnamed pipe (FIFO)made with mkfifo
ssocket: a communication endpoint/run/... on full systems
$ ls -ld /etc /bin /dev/null
lrwxrwxrwx. 1 root root    7 Jan  1  2025 /bin -> usr/bin
crw-rw-rw-. 1 root root 1, 3 Nov  1 14:00 /dev/null
drwxr-xr-x. 1 root root 1262 Nov  1 09:01 /etc

For a device, the size column is replaced by two numbers (major, minor) that identify the driver in the kernel.

Notice what the type does not include: whether something is a text file, a JPEG or a program. To the kernel those are all regular files, just bytes. Extensions like .txt and .jpg are a naming convention for humans and some applications; the kernel ignores them.

The file command: what is in it?

file reads the first bytes of a file and compares them with a database of known signatures (magic numbers), so it tells you what the content actually is, whatever the name says:

$ file /etc/hostname /usr/bin/ls /bin /dev/null
/etc/hostname: ASCII text
/usr/bin/ls:   ELF 64-bit LSB pie executable, x86-64, ...
/bin:          symbolic link to usr/bin
/dev/null:     character special (1/3)

A gzip file always starts with the bytes 1f 8b, a PNG with 89 50 4E 47 (.PNG), a script with #!. Use file before you cat something unknown; dumping a binary file to the terminal can scramble it (if it happens, type reset and press Enter).

stat: everything the kernel knows

stat prints the full record:

$ stat survey/notes.txt
  File: survey/notes.txt
  Size: 1200            Blocks: 8          IO Block: 4096   regular file
Device: 0,52    Inode: 16431       Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/ learner)   Gid: ( 1000/ learner)
Access: 2026-11-01 14:20:03.000000000 +0000
Modify: 2026-11-01 14:20:03.000000000 +0000
Change: 2026-11-01 14:20:03.000000000 +0000
 Birth: 2026-11-01 14:20:03.000000000 +0000

What happens underneath: the inode

A file on a Unix filesystem has two parts:

 directory "survey"              inode 16431                     data blocks
 ┌───────────────┬───────┐      ┌──────────────────────────┐     ┌──────────┐
 │ notes.txt     │ 16431 ├─────▶│ type: regular file        │────▶│ 1200     │
 │ report.pdf    │ 16432 │      │ size: 1200, blocks: 8     │     │ bytes of │
 │ ...           │       │      │ owner 1000, group 1000    │     │ content  │
 └───────────────┴───────┘      │ mode 0644, links: 1       │     └──────────┘
                                │ atime, mtime, ctime       │
                                └──────────────────────────┘

ls -l and stat mostly show you the inode. The name lives only in the directory, which is why renaming a file does not change its modification time, and why one file can have two names (module 3).

The three classic timestamps:

stat -c prints chosen fields, handy in scripts: stat -c '%s %n' survey/* prints size and name; %i is the inode, %U the owner, %a the permissions in octal.

How much space?

ls -l shows the size of each file, but not the total size of a directory's contents. That is du (disk usage):

$ du -sh survey
32K     survey
$ du -h survey
8.0K    survey/archive
32K     survey

-s gives just the total, -h human units. df -h (previous lesson) shows the free space of whole filesystems.

Common mistakes

Recap

Going further: run stat /dev/null /proc/uptime and compare them with a normal file. Then ls -li /usr/bin | sort -n | head: inode numbers in a real directory.

Exercises

Exercise 1 · quiz

Read a long listing

ls -l prints this line:

drwxr-x---. 3 ana staff 4096 Mar 2 10:15 projects

Which statements are true? Select all that apply.

Answer
  • projects is a directory.

  • The file's owner is ana and its group is staff.

  • Mar 2 10:15 is when projects was last modified.

The first character is the type (d directory, - regular file, l symbolic link). Then come permissions, the link count, owner, group, size in bytes, modification time and name. For a directory the size is the space the directory's own list of names takes up (often one 4096-byte block), not the number or size of the files inside it. du -sh projects would tell you how much space its contents use.

Exercise 2 · task in a shell

Survey a directory

The directory /work/survey contains a mixture of files, some of them hidden. Use ls with the right options to answer two questions, and write each answer into a file in /work:

  1. biggest.txt: the name of the largest regular file in survey (hidden ones included). Just the name, like something.dat.
  2. hidden.txt: the number of hidden entries in survey (names starting with a dot, not counting . and ..).

Useful options: -a (all, including hidden), -A (like -a but without . and ..), -l (long), -S (sort by size, largest first), -h (human-readable sizes).

Hint 1

ls -lAS survey lists everything except . and .., largest first. A directory (archive) is not a regular file.

Hint 2

The largest is a hidden file. Count the names that start with a dot in ls -A survey.

Reference solution
echo .cache.db > biggest.txt
echo 3 > hidden.txt

Exercise 3 · task in a shell

Names can lie

In /work/evidence there are four files. Their extensions do not necessarily match their contents. Use the file command to find out what each one really is, then write the name of the one that is actually gzip compressed data into /work/gzip.txt, and the name of the one that is really a directory into /work/dir.txt.

Hint 1

file evidence/* describes each entry on its own line.

Hint 2

notes.txt is the gzip file and report.pdf is the directory.

Reference solution
echo notes.txt > gzip.txt
echo report.pdf > dir.txt