ls
List directory contents
ls lists what is in a directory. Given no arguments it lists the current one, given a directory
it lists that directory's contents, and given a file it lists that file.
Its output changes depending on where it is going
This catches people out when a command works at the prompt and behaves differently in a script.
ls checks whether its standard output is a terminal. When it is, it arranges names in columns
across the width of the window and may add colour. When output goes to a pipe or a file, it
switches to one name per line and drops the colour.
That is why ls | wc -l counts entries correctly while the same listing on screen shows four
across a line. Both are ls doing what it was asked. -1 forces one per line and -C forces
columns, so a script that cares should say which it wants rather than inherit the default.
Terminal, shell and tty covers the check itself, and what
else changes with it.
Every example on this page was captured through a pipe, so the listings below are the one-per-line form. At a terminal, the short ones will appear in columns.
The long format, column by column
-l is the flag worth knowing properly, because six of its seven columns answer a different
question:
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
The first character is the entry type: - for a regular file, d for a directory, l for a
symlink. The next nine are the permission bits in three groups of three, covered in
file permissions explained. Then the link count, the
owner, the group, the size in bytes, the modification time, and the name.
Two of those mislead if taken at face value. The size of a directory (usually 4096) is the
size of the directory file itself, rather than of what it contains; adding up a tree's real size
is du's job, as in
find the largest files. And the link
count on a directory is the number of subdirectories it holds plus two, because every directory
contains . and every child of it contains ...
The total line above the listing is disk blocks used by that directory's entries, in units of
1024 bytes by default. It counts allocated blocks rather than bytes, so it will not match the
sum of the size column, and it ignores subdirectory contents.
Hidden files, and the two flags for them
A leading dot in a filename hides that file from a default listing. -a shows everything
including the . and .. entries that every directory contains; -A shows everything except
those two. -A is almost always the one you want, even though -a is the one everybody types.
Sorting
The default is by name, using the locale's collation rules rather than raw byte order, so
LC_COLLATE decides whether Cherry sorts before apple. The other orders are -t by
modification time, -S by size, -X by extension and -v by version number, which sorts
file10 after file9 where the default sorts it before. Each of them puts the largest or most
recent first, and -r reverses whichever order is in effect.
ls -ltr is worth committing to memory: long format, sorted by time, reversed, so the most
recently modified file is the last line printed and does not scroll away.
Which timestamp
ls -l shows the modification time, when the file's contents last changed. -u shows the access
time and -c the change time, which is when the inode last changed: a chmod or a rename updates
ctime without touching mtime. There is no creation time here, because most Linux filesystems
did not record one until recently and ls still does not read it.
The date format depends on the age. Files modified within the last six months show a month, day
and time; older ones show a month, day and year, on the grounds that the year is more important
than the minute once something is that old. --time-style=long-iso gives 2026-06-21 10:00 for
everything and is the better choice whenever the output will be read by anything other than a
person.
Symlinks
ls -l shows a symlink as l with an arrow to its target, and reports the size of the link
itself rather than of what it points at. -L follows the link and describes the target instead.
A link whose target no longer exists is still listed without complaint, which is why a broken
symlink is easy to miss.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
projects/ the same sample tree the find page uses: nested directories, two symlinks (one broken), a hidden directory, and files of deliberately different sizes, modes and ages
projects:
total 20
drwxr-xr-x 2 user user 4096 Jun 26 10:00 .hidden
drwxr-xr-x 2 user user 4096 Jun 26 10:00 backups
drwxr-xr-x 2 user user 4096 Jun 26 10:00 empty-dir
drwxr-xr-x 2 user user 4096 Jun 26 10:00 logs
drwxr-xr-x 3 user user 4096 Jun 26 10:00 src
projects/.hidden:
total 4
-rw-r--r-- 1 user user 13 Jun 24 10:00 .env
projects/backups:
total 16
-rw------- 1 user user 5120 May 31 2025 site-2026-01-01.tar.gz
-rw-r--r-- 1 user user 5120 Jun 15 15:30 site-2026-06-01.tar.gz
projects/empty-dir:
total 0
projects/logs:
total 6156
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
projects/src:
total 12
-rwxrwxrwx 1 user user 8 Jun 23 10:00 main.js
-rw-r--r-- 1 user user 8 Jun 1 09:00 util.js
drwxr-xr-x 2 user user 4096 Jun 26 10:00 vendor
projects/src/vendor:
total 4
-rw-r--r-- 1 user user 7 Jun 22 10:00 lib.js
Meeting Notes (final).docx an empty file whose name contains spaces and brackets, beside the tree rather than inside it
-rw-r--r-- 1 user user 0 Jun 27 11:00 Meeting Notes (final).docx
Listing a directory
Remember that every listing on this page arrived through a pipe, so ls used its one-name-per-line form. At a terminal the same commands print in columns.
List the current directory
ls
With no arguments, the working directory, wherever cd last left you. The sample tree and one loose file are all that is here.
Show output
Meeting Notes (final).docx
projects
List a named directory
ls projects
Given a directory, ls lists what is inside it rather than the directory itself. The hidden .hidden entry is left out.
Show output
backups
empty-dir
logs
src
Force one name per line
ls -1 projects
-1 states the one-per-line layout rather than leaving it to be inferred from where the output is going, which is what a script should do.
Show output
backups
empty-dir
logs
src
Force columns even into a pipe
ls -C projects
-C forces the opposite: columns even when standard output is not a terminal. Comparing this against the example above is the clearest way to see the rule.
Show output
backups empty-dir logs src
Fill rows instead of columns
ls -x projects
-C orders entries down each column, -x orders them across each row. With four short names the difference is invisible; with a hundred it decides where any given name appears.
Show output
backups empty-dir logs src
Separate names with commas
ls -m projects
-m gives a comma-separated list, which suits pasting a set of names into something else.
Show output
backups, empty-dir, logs, src
List the directory itself rather than its contents
ls -d projects
-d stops ls descending into a directory it was given. On its own it just echoes the name back.
Show output
projects
Describe a directory without listing it
ls -ld projects
-ld together is the useful form: the directory's own mode, owner and size, with none of its contents. The usual way to check a directory's permissions.
Show output
drwxr-xr-x 7 user user 4096 Jun 26 10:00 projects
List a single file
ls -l projects/src/main.js
Given a file rather than a directory, ls describes that file. Note the mode: this one is world-writable, which ls -l makes obvious at a glance.
Show output
-rwxrwxrwx 1 user user 8 Jun 23 10:00 projects/src/main.js
List two directories at once
ls -l projects/src projects/backups
Several arguments each get a heading and a blank line between them. The arguments are sorted, so backups is printed first whatever order they were typed in.
Show output
projects/backups:
total 16
-rw------- 1 user user 5120 May 31 2025 site-2026-01-01.tar.gz
-rw-r--r-- 1 user user 5120 Jun 15 15:30 site-2026-06-01.tar.gz
projects/src:
total 12
-rwxrwxrwx 1 user user 8 Jun 23 10:00 main.js
-rw-r--r-- 1 user user 8 Jun 1 09:00 util.js
drwxr-xr-x 2 user user 4096 Jun 26 10:00 vendor
List an empty directory
ls projects/empty-dir; echo "exit: $?"
Nothing to print, and exit status 0. An empty listing is not an error.
Show output
exit: 0
Ask for something that is not there
ls nosuchdir; echo "exit: $?"
ls exits 2 for a serious problem, not 1. Worth knowing when a script tests the status.
Show output
ls: cannot access 'nosuchdir': No such file or directory
exit: 2
Mix a real target with a missing one
ls projects nosuchdir; echo "exit: $?"
The error comes first, on standard error, and the listing it could produce still follows on standard output. The exit status reports the failure even though most of the work succeeded.
Show output
ls: cannot access 'nosuchdir': No such file or directory
projects:
backups
empty-dir
logs
src
exit: 2
The long format
-l prints one entry per line with its type, permissions, link count, owner, group, size, timestamp and name.
Show the long format
ls -l projects
The name is preceded by five columns, and a total line reports the blocks the entries occupy. Every entry here is a directory, hence the leading d and the 4096 size.
Show output
total 16
drwxr-xr-x 2 user user 4096 Jun 26 10:00 backups
drwxr-xr-x 2 user user 4096 Jun 26 10:00 empty-dir
drwxr-xr-x 2 user user 4096 Jun 26 10:00 logs
drwxr-xr-x 3 user user 4096 Jun 26 10:00 src
Read sizes as bytes
ls -l projects/logs
Sizes are exact byte counts by default, which is right for a script and hard work for a person once the numbers get long.
Show output
total 6156
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Read sizes as human-readable units
ls -lh projects/logs
-h rounds to K, M and G using powers of 1024, and the total line is converted too. The single most-typed ls flag after -l itself.
Show output
total 6.1M
-rw-r--r-- 1 user user 10K Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6.0M Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Use powers of 1000 instead
ls --si -lh projects/logs
--si switches to powers of 1000, matching how drive manufacturers count. The same file is 6.0M either way here only because the rounding hides the difference.
Show output
total 6.1M
-rw-r--r-- 1 user user 10K Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6.0M Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Report every size in a fixed unit
ls -l --block-size=K projects/logs
--block-size rounds every size to the unit you name, so the column stays comparable instead of mixing K and M. Note that a 14-byte symlink rounds up to 1K.
Show output
total 6156K
-rw-r--r-- 1 user user 10K Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6144K Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 1K Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 1K Jun 25 10:00 rotated-link -> app.log.1.gz
Show allocated blocks rather than byte counts
ls -s projects/logs
-s prints the blocks each entry occupies on disk. app.log holds 10240 bytes and occupies 12 blocks, and the symlinks occupy none at all, because their targets fit inside the inode.
Show output
total 6156
12 app.log
6144 big.log
0 main-link.js
0 rotated-link
Drop the group column
ls -lG projects/logs
-G suppresses the group, which is noise whenever every file belongs to its owner's own group.
Show output
total 6156
-rw-r--r-- 1 user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Use the long format without the group in one flag
ls -l -o projects/logs
-o is -l with the group column already dropped, so it does the same job as -lG in fewer characters.
Show output
total 6156
-rw-r--r-- 1 user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Read just the total line
ls -l projects | head -1
The total counts blocks used by the entries in this directory only, ignoring what the subdirectories contain. See head for why the pipeline stops there.
Show output
total 16
See a directory that contains nothing
ls -l projects/empty-dir
An empty directory still prints a total, and it is 0.
Show output
total 0
Hidden files
A leading dot keeps an entry out of the default listing. It is a naming convention that ls honours rather than a filesystem attribute.
Changing the order
The default is by name in the locale's collation order. Every other order is a flag, and -r reverses whichever one is in effect.
Sort by modification time, newest first
ls -lt projects/logs
-t orders by mtime rather than name. The symlinks were touched last, so they come first.
Show output
total 6156
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
Sort by time with the newest last
ls -ltr projects/logs
-r reverses it, so the most recent entry is the final line. The form to use on a long listing, where the top scrolls away and the bottom does not.
Show output
total 6156
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
Sort by size, largest first
ls -lS projects/logs
-S orders by byte count. Symlinks sort by their own size rather than their target's, which is why both land at the bottom.
Show output
total 6156
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Sort by size, smallest first
ls -lSr projects/logs
The same reversal applies to -S, which finds the smallest files rather than the largest.
Show output
total 6156
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
Sort by extension
ls -lX projects/src
-X groups by whatever follows the final dot, with entries that have no extension first. Directories have none, so vendor leads.
Show output
total 12
drwxr-xr-x 2 user user 4096 Jun 26 10:00 vendor
-rwxrwxrwx 1 user user 8 Jun 23 10:00 main.js
-rw-r--r-- 1 user user 8 Jun 1 09:00 util.js
Reverse the default name order
ls -lr projects
With no other sort flag, -r simply reverses the alphabetical listing.
Show output
total 16
drwxr-xr-x 3 user user 4096 Jun 26 10:00 src
drwxr-xr-x 2 user user 4096 Jun 26 10:00 logs
drwxr-xr-x 2 user user 4096 Jun 26 10:00 empty-dir
drwxr-xr-x 2 user user 4096 Jun 26 10:00 backups
Sort names containing numbers naturally
ls -lv projects/logs
-v compares runs of digits as numbers, so app10.log would follow app9.log instead of preceding it. With no numbered names here it matches the default order.
Show output
total 6156
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Put directories before files
ls --group-directories-first projects
Sorts directories into their own block ahead of everything else, keeping alphabetical order within each block. A GNU extension, so not available on a BSD or macOS ls.
Show output
backups
empty-dir
logs
src
Timestamps
Every file carries three times and ls can show any of them, but none of the three is a creation time. touch is what sets the two that can be set.
See how the date format changes with age
ls -l projects/backups
The 2025 file is older than six months, so ls prints its year in place of the time; the recent one keeps its 15:30.
Show output
total 16
-rw------- 1 user user 5120 May 31 2025 site-2026-01-01.tar.gz
-rw-r--r-- 1 user user 5120 Jun 15 15:30 site-2026-06-01.tar.gz
Print dates in a consistent format
ls -l --time-style=long-iso projects/logs
long-iso gives 2026-06-21 10:00 for every entry regardless of age, which is what you want whenever the output will be sorted, compared or parsed.
Show output
total 6156
-rw-r--r-- 1 user user 10240 2026-06-21 10:00 app.log
-rw-r--r-- 1 user user 6291456 2026-06-20 10:00 big.log
lrwxrwxrwx 1 user user 14 2026-06-25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 2026-06-25 10:00 rotated-link -> app.log.1.gz
Print the full timestamp with timezone
ls -l --time-style=full-iso projects/backups
Nanosecond precision and an explicit offset, which is more than a person needs and exactly what a script comparing two machines needs.
Show output
total 16
-rw------- 1 user user 5120 2025-05-31 12:00:00.000000000 +0000 site-2026-01-01.tar.gz
-rw-r--r-- 1 user user 5120 2026-06-15 15:30:00.000000000 +0000 site-2026-06-01.tar.gz
Choose the date format yourself
ls -l --time-style=+%Y-%m-%d projects/logs
A leading + takes a date-style format string, so the timestamp can be cut down to just the part being read.
Show output
total 6156
-rw-r--r-- 1 user user 10240 2026-06-21 app.log
-rw-r--r-- 1 user user 6291456 2026-06-20 big.log
lrwxrwxrwx 1 user user 14 2026-06-25 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 2026-06-25 rotated-link -> app.log.1.gz
Show the access time instead
ls -lu projects/logs
-u reports when each file was last read rather than last written. Most systems mount with relatime, which only updates it once a day, so it is a weaker signal than it looks.
Show output
total 6156
-rw-r--r-- 1 user user 10240 Jun 21 10:00 app.log
-rw-r--r-- 1 user user 6291456 Jun 20 10:00 big.log
lrwxrwxrwx 1 user user 14 Jun 25 10:00 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Jun 25 10:00 rotated-link -> app.log.1.gz
Show the inode change time
ls -lc projects/logs
-c reports when the inode last changed, which a chmod, a rename or a new hard link updates without touching the contents. It cannot be set with touch, so here it is when the sandbox created these files rather than the dates the other examples show.
Show output
Your output will differ: the change time is when your copy of these files was created, so it will be today's date rather than the one below
total 6156
-rw-r--r-- 1 user user 10240 Aug 23 14:08 app.log
-rw-r--r-- 1 user user 6291456 Aug 23 14:08 big.log
lrwxrwxrwx 1 user user 14 Aug 23 14:08 main-link.js -> ../src/main.js
lrwxrwxrwx 1 user user 12 Aug 23 14:08 rotated-link -> app.log.1.gz
Symlinks and entry types
ls describes a symlink itself unless told otherwise, which is usually what you want and occasionally the opposite. ln covers making them.
See where a symlink points
ls -l projects/logs/main-link.js
The l in the first column marks a symlink, and the arrow gives its target. The size, 14, is the length of the target path rather than the size of the file it names.
Show output
lrwxrwxrwx 1 user user 14 Jun 25 10:00 projects/logs/main-link.js -> ../src/main.js
Describe the target instead of the link
ls -lL projects/logs/main-link.js
-L dereferences, so the mode, size and time all belong to main.js. The name printed is still the one you asked for, which makes this easy to misread.
Show output
-rwxrwxrwx 1 user user 8 Jun 23 10:00 projects/logs/main-link.js
List a symlink whose target is gone
ls -l projects/logs/rotated-link
A broken link lists exactly like a working one, with no error and no marker. ls -lL on this would fail instead, which is one way to find them.
Show output
lrwxrwxrwx 1 user user 12 Jun 25 10:00 projects/logs/rotated-link -> app.log.1.gz
Mark each entry with its type
ls -F projects
-F appends a character saying what each entry is: / for a directory, @ for a symlink, * for an executable, nothing for a plain file.
Show output
backups/
empty-dir/
logs/
src/
Mark only the directories
ls -p projects
-p adds the trailing slash for directories and leaves everything else alone, which keeps the names copy-pasteable in a way -F does not.
Show output
backups/
empty-dir/
logs/
src/
Recursion
-R walks the whole tree, printing a heading for each directory it reaches.
List a tree
ls -R projects
Each directory gets a path: heading and a blank line before the next. .hidden is missing because -R does not imply -a.
Show output
projects:
backups
empty-dir
logs
src
projects/backups:
site-2026-01-01.tar.gz
site-2026-06-01.tar.gz
projects/empty-dir:
projects/logs:
app.log
big.log
main-link.js
rotated-link
projects/src:
main.js
util.js
vendor
projects/src/vendor:
lib.js
Walk a tree in long format
ls -lR projects/src
-lR is what the sample-file block at the top of this page is captured with, since it records modes, sizes and dates for a whole tree in one command.
Show output
projects/src:
total 12
-rwxrwxrwx 1 user user 8 Jun 23 10:00 main.js
-rw-r--r-- 1 user user 8 Jun 1 09:00 util.js
drwxr-xr-x 2 user user 4096 Jun 26 10:00 vendor
projects/src/vendor:
total 4
-rw-r--r-- 1 user user 7 Jun 22 10:00 lib.js
Names that need quoting
A filename may contain spaces, quotes and newlines. Since coreutils 8.25 ls quotes such names by default when writing to a terminal, and this is one of the few places where its piped output differs in substance rather than layout.
List a name containing spaces
ls *.docx
Through a pipe the name is printed literally, exactly as it would need to be typed back. At a terminal ls would wrap it in single quotes instead.
Show output
Meeting Notes (final).docx
Escape the spaces instead of quoting
ls -b *.docx
-b backslash-escapes anything awkward, which is the form to paste straight back into a shell command.
Show output
Meeting\ Notes\ (final).docx
Wrap names in double quotes
ls -Q *.docx
-Q is the C-style quoting shell scripts and other programs tend to expect.
Show output
"Meeting Notes (final).docx"
Turn quoting off entirely
ls --quoting-style=literal *.docx
literal prints the raw bytes with no escaping at all. Correct when feeding a single known name to something else, and a hazard on names you have not inspected.
Show output
Meeting Notes (final).docx
Describe an awkward name in full
ls -l 'Meeting Notes (final).docx'
The quoting on the command line is the shell's business rather than ls's: without it the shell would pass three separate arguments.
Show output
-rw-r--r-- 1 user user 0 Jun 27 11:00 Meeting Notes (final).docx