Hidden Files and ls Options
In Linux, any file or directory whose name begins with a dot (.) is treated as hidden. It exists on disk exactly like any other file, but the shell and most command-line tools quietly leave it out of a normal directory listing. Hidden files are everywhere: your shell’s startup scripts, your Git repository’s metadata, your SSH keys, and countless application settings all live as dotfiles in your home directory. The ls command is your primary window into the filesystem, so knowing its options for revealing, sorting, and formatting hidden files is essential. This lesson covers exactly how hiding works, the full set of useful ls options, and the mistakes people make when they forget hidden files exist.
Overview: What Actually Makes a File “Hidden”?
Unlike Windows, Linux has no special “hidden” attribute stored in the filesystem. A file’s hidden status is determined purely by convention: if its filename starts with a dot, tools like ls and the shell’s own wildcard expansion (globbing) skip over it by default. The kernel does not know or care that a file is “hidden” — when a program calls readdir() to list a directory’s contents, it gets back every entry, dotfiles included. Filtering happens entirely in userspace, inside ls itself.
This convention traces back to early Unix. Every directory contains two special entries: ., which points back to the directory’s own inode, and .., which points to the parent directory’s inode. These are literally how cd .. and cd . work — they are real directory entries, not shell tricks. Early versions of ls skipped any entry starting with a dot so that . and .. wouldn’t clutter every single listing. Programmers noticed this side effect and started naming configuration files with a leading dot on purpose, so their clutter would be hidden the same way. That accidental behavior became a firm, universal convention.
Because hiding is cosmetic rather than a security feature, a dotfile is exactly as readable, writable, and executable as any other file — standard Linux permissions still fully apply. A file named .env with permissions 644 is still world-readable; hiding it from a casual ls does nothing to protect its contents. If a file holds secrets, you still need to lock it down with chmod 600.
Common hidden files you’ll encounter
~/.bashrcand~/.profile— shell startup configuration~/.bash_history— your command history~/.ssh/— SSH keys and known hosts~/.gitconfigand a project’s.git/directory — Git identity and repository metadata~/.config/— a standard location many applications use for settings.env— environment variables for a project, often containing credentials
Syntax
The general form of the command is:
ls [OPTION]... [FILE]...
With no arguments, ls lists the current directory. Give it one or more paths to list those instead. The table below covers the options you’ll use constantly.
| Option | Meaning |
|---|---|
-a |
Show all entries, including dotfiles and the . / .. entries |
-A |
Like -a, but hides . and .. (“almost all”) — usually what you actually want |
-l |
Long format: permissions, link count, owner, group, size, modification time, name |
-h |
With -l, print sizes in human-readable units (K, M, G) instead of raw bytes |
-t |
Sort by modification time, newest first, instead of alphabetically |
-r |
Reverse the current sort order |
-R |
List subdirectories recursively |
-d |
List a directory itself as an entry, instead of descending into it |
-F |
Append a marker to names showing type (/ for directories, * for executables) |
-S |
Sort by file size, largest first |
--color=auto |
Colorize output by file type (usually already the default via a shell alias) |
Examples
Example 1: A plain listing hides the dotfiles.
ls ~/projects
Output:
notes.txt report.pdf src
This looks like the whole story, but it isn’t. The directory also contains a .git repository and a .env file — ls simply isn’t showing them because neither -a nor -A was given.
Example 2: Revealing hidden entries with -a.
ls -a ~/projects
Output:
. .. .env .git notes.txt report.pdf src
Now every entry is visible, including . (this directory) and .. (its parent). Two previously invisible items appear: .env and .git. This is the moment most beginners realize a directory they thought was simple actually holds a lot more.
Example 3: Combining flags for a detailed, sorted, human-readable view.
ls -laht ~/projects
Output:
total 76K
drwxr-xr-x 5 alice alice 4.0K Aug 3 14:22 .
drwx------ 8 alice alice 4.0K Aug 3 14:22 .git
-rw------- 1 alice alice 112 Aug 2 18:03 .env
drwxr-xr-x 12 alice alice 4.0K Aug 1 09:10 ..
-rw-r--r-- 1 alice alice 256 Jul 30 11:45 notes.txt
-rw-r--r-- 1 alice alice 48K Jul 29 16:20 report.pdf
drwxr-xr-x 3 alice alice 4.0K Jul 28 10:00 src
Reading a long-format line left to right: the first character is the file type (- for a regular file, d for a directory), followed by three permission triplets for owner, group, and other. Then comes the link count, the owner and group names, the size (in human-readable units thanks to -h), the last modification time, and finally the name. Because -t was used, the newest entries (.git and .env, both touched today) appear first instead of alphabetically. Notice too that .git is mode 700 (only the owner can enter it) and .env is mode 600 (only the owner can even read it) — sensible permissions for anything containing repository internals or secrets.
How ls Works Step by Step
When you run ls against a directory, several things happen in sequence:
lsopens the directory withopendir()and repeatedly callsreaddir(), which returns every entry the kernel has recorded — filename and inode number for each, dotfiles and all.- Unless
-aor-Awas passed,lsfilters out any entry whose name starts with.before doing anything else. This filtering is purely a userspace decision made by thelsprogram, not the kernel. - If you asked for extra metadata (
-l,-h,-t,-S),lscallsstat()on each remaining entry to fetch its inode information: permission bits, owner and group IDs, size, and timestamps. This is whyls -lon a huge directory is noticeably slower than a barels— it’s doing one extra system call per file. - The remaining entries are sorted. The default is alphabetical by filename;
-tswitches to modification time (newest first),-Sto size (largest first), and-rreverses whichever order was chosen. - Finally,
lsformats and prints the result — in columns for a bare listing, one entry per line for-l, and with ANSI color codes if--coloris active (most distributions enable this by default through a shell alias likealias ls='ls --color=auto').
Common Mistakes
Mistake 1: Assuming rm * empties a directory completely. The shell expands * before rm ever runs, and shell globbing never matches dotfiles by default — the same convention ls follows. Someone trying to fully clear out an old project directory might run this and be surprised later:
cd ~/old-project
rm *
This deletes notes.txt, report.pdf, and src, but .git and .env are left behind untouched, silently consuming disk space and potentially leaving credentials on disk. If the goal is to remove everything — hidden files included — target the directory itself instead of relying on a wildcard:
cd ~
rm -rf ~/old-project
Mistake 2: Trying to list only hidden files with ls .*. This looks reasonable, but the shell expands .* to match every dotfile including . and .. themselves:
ls .*
Because .. is included in that expansion, ls receives the parent directory as one of its arguments and happily lists its entire contents too — producing a confusing mashup of the current directory’s dotfiles and the parent directory’s full listing. Use the purpose-built flag instead, which excludes . and .. automatically:
ls -A ~/projects
Mistake 3: Forgetting -d when you want the entry, not its contents. If you pass a directory name directly to ls, it lists what’s inside that directory, not the directory entry itself:
ls -a .config
Instead of confirming that a .config directory exists, this dumps everything stored inside it — potentially dozens of unrelated application folders. When you want information about the directory itself (its permissions, its size as an entry, or simply to confirm it exists) without descending into it, add -d:
ls -d .config
Best Practices
- Get in the habit of running
ls -a(or-A) before deleting, archiving, or copying a directory — an apparently empty or simple directory often isn’t. - Prefer
-Aover-ain day-to-day use and in scripts; excluding.and..avoids off-by-two surprises when counting or iterating over entries. - Never assume hiding equals security. Set restrictive permissions (
chmod 600for secret files,chmod 700for private directories) on any dotfile that holds credentials. - When auditing what’s really in a directory, combine flags:
ls -lahtgives you type, permissions, owner, human-readable size, and recency all at once. - Don’t try to parse
lsoutput in scripts (filenames can contain spaces, newlines, or dashes that break naive parsing). Use globs,find, or Bash’s own filename expansion instead. - If you need shell globs to match dotfiles too, enable the
dotglobshell option (shopt -s dotglob) rather than trying to hand-craft a pattern like.[^.]*.
Practice Exercises
- Create a hidden file called
.secretsinside a scratch directory (for example~/practice). Run a plainls, thenls -a, thenls -A, and note exactly which entries appear in each case and why. - Run
ls -ld ~/.ssh(if you have one) and read the permission bits. Explain in your own words why an SSH client refuses to work if that directory is more permissive than700. - Write a one-line command that counts how many hidden files (excluding
.and..) exist directly inside your home directory. Hint: combinels -Awithwc -l, keeping in mind this approach can miscount if any filename contains a newline.
Summary
- A “hidden” file is just a file whose name starts with
.— there is no special kernel-level hidden attribute, and normal permissions still fully apply. .and..are real directory entries pointing to the current and parent directory’s inodes, not shell shortcuts.ls -ashows everything, including.and..;ls -Ashows everything except those two, which is usually what you want.ls -ladds permissions, ownership, size, and timestamp; add-hfor human-readable sizes and-tto sort by recency.- Shell wildcards like
*never match dotfiles by default, which is whyrm *quietly leaves hidden files behind. - Use
ls -dwhen you want information about a directory entry itself instead of listing what’s inside it.
