Blog

Python ls: How to List Files in a Directory (os, pathlib, glob)

Python has no built-in ls. To list the files in a directory, use os.listdir() for a quick list of names, pathlib.Path.iterdir() for modern code, glob or Path.glob() to match patterns like *.py, and os.scandir() when you also need file sizes or types. The examples here were run with Python 3 on Linux, against this folder:

demo/
  a.txt
  b.py
  .hidden
  .git/
  sub/
    c.py

os.listdir: the plain list of names

import os

print(sorted(os.listdir("demo")))
# ['.git', '.hidden', 'a.txt', 'b.py', 'sub']

os.listdir(path) returns a list of names (not full paths) of everything in the folder, files and subfolders alike, including hidden files. It never includes . and ... With no argument it lists the current directory.

The order is arbitrary, whatever the operating system returns. If order matters, wrap it in sorted(), as above. To drop hidden entries, as plain ls does:

visible = [n for n in sorted(os.listdir("demo")) if not n.startswith(".")]
print(visible)
# ['a.txt', 'b.py', 'sub']

pathlib: the modern way

pathlib (standard library since Python 3.4) gives you Path objects instead of strings, so you can chain checks and joins.

from pathlib import Path

p = Path("demo")
print(sorted(x.name for x in p.iterdir()))
# ['.git', '.hidden', 'a.txt', 'b.py', 'sub']

print(sorted(x.name for x in p.glob("*.py")))
# ['b.py']

print(sorted(str(x) for x in p.rglob("*.py")))
# ['demo/b.py', 'demo/sub/c.py']

print(sorted(str(x) for x in p.glob("*") if x.is_file()))
# ['demo/.hidden', 'demo/a.txt', 'demo/b.py']
  • iterdir() yields every entry, including hidden ones, as a full Path.
  • glob("*.py") matches in that folder only.
  • rglob("*.py") searches the folder and all subfolders. It is the same as glob("**/*.py").
  • is_file() and is_dir() let you keep only files or only folders.

Notice that glob("*") still returned demo/.hidden in the last example. Hidden-file handling in glob differs from the shell, as the next section shows.

glob: patterns, shell style

import glob

print(sorted(glob.glob("demo/*.py")))
# ['demo/b.py']

print(sorted(glob.glob("demo/**/*.py", recursive=True)))
# ['demo/b.py', 'demo/sub/c.py']

print(sorted(glob.glob("demo/*")))
# ['demo/a.txt', 'demo/b.py', 'demo/sub']

print(sorted(glob.glob("demo/.*")))
# ['demo/.git', 'demo/.hidden']

The glob module follows shell rules: * does not match names that start with a dot, so hidden files appear only when you ask for them with a pattern like .*. This is the closest match to what ls demo/* does in a terminal. The ** pattern needs recursive=True, otherwise ** acts like a single *.

os.scandir: names plus file info, without extra calls

If you need sizes, types or modification times, os.scandir() is the efficient choice. Each entry comes with type information already known, so checking is_dir() usually does not need another system call, which matters on large folders.

import os

with os.scandir("demo") as it:
    for e in sorted(it, key=lambda e: e.name):
        print(e.name, e.is_dir(), e.is_file(), e.stat().st_size if e.is_file() else "-")
# .git True False -
# .hidden False True 1
# a.txt False True 5
# b.py False True 4
# sub True False -

Use it as a context manager (with) so the directory handle closes cleanly. The entries are DirEntry objects, not strings: use e.name for the name and e.path for the path.

A small ls -l in Python

import os
from datetime import datetime

def ls_l(path="."):
    for e in sorted(os.scandir(path), key=lambda e: e.name.lower()):
        if e.name.startswith("."):
            continue                      # skip hidden, like plain ls
        st = e.stat()
        kind = "d" if e.is_dir() else "-"
        when = datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d")
        print(f"{kind} {st.st_size:>8} {when} {e.name}")

ls_l("demo")
# -        5 2026-10-15 a.txt
# -        4 2026-10-15 b.py
# d     4096 2026-10-15 sub

Your dates will differ. The size shown for a folder is how much space the folder entry itself takes, not what is inside it, and the number depends on the file system.

Walking a whole tree with os.walk

When you need every file below a folder, os.walk() gives you the folder, its subfolders and its files at each level. Changing the dirs list in place stops it from descending into folders you want to skip, such as .git:

import os

for folder, dirs, files in os.walk("demo"):
    dirs[:] = [d for d in dirs if not d.startswith(".")]   # skip hidden folders
    for name in sorted(files):
        print(os.path.join(folder, name))
# demo/.hidden
# demo/a.txt
# demo/b.py
# demo/sub/c.py

To list the newest files first, sort by modification time: sorted(Path("demo").iterdir(), key=lambda p: p.stat().st_mtime, reverse=True). This is the equivalent of ls -t.

Which one should you use?

Need Use
Quick list of names os.listdir(path)
New code, path joining, checks Path(path).iterdir()
Only files matching a pattern Path.glob() or glob.glob()
Everything under a folder Path.rglob() or os.walk()
Sizes, types or times for many files os.scandir()

Errors you will hit

import os

for bad in ["nope", "demo/a.txt"]:
    try:
        os.listdir(bad)
    except Exception as ex:
        print(type(ex).__name__, ex)
# FileNotFoundError [Errno 2] No such file or directory: 'nope'
# NotADirectoryError [Errno 20] Not a directory: 'demo/a.txt'

In real code catch the specific ones: FileNotFoundError (path does not exist), NotADirectoryError (it is a file), and PermissionError (you cannot read it). The most common bug is a relative path that depends on where the script runs. If a script works in your editor and fails from the terminal, print os.getcwd(), or build paths from the script’s own location with Path(__file__).parent.

Another common bug: os.listdir() returns names, not paths. os.path.getsize(name) fails unless the current directory is the folder you listed. Join first with os.path.join(folder, name), or use pathlib or scandir, which give you full paths.

Calling the real ls

You can run the system’s ls from Python with subprocess, but it is rarely a good idea: it breaks on Windows, and you have to parse text. If you must:

import subprocess

r = subprocess.run(["ls", "-1A", "demo"], capture_output=True, text=True, check=True)
print(sorted(r.stdout.split()))
# ['.git', '.hidden', 'a.txt', 'b.py', 'sub']

In Jupyter and IPython, %ls and !ls work as shortcuts, but they are notebook features, not Python.

Practice

Listing files is a small loop over a list, so the practice is the same as for any loop: filter, sort, and build a result. If that part is shaky, work through the lists lesson and the functions lesson in the AlgoCademy Python course. For a related Python list task, see removing None from a list in Python.

A lesson in AlgoCademy: the step to write, a picture of what it does, the line written in the editor, and the check: Step 2 passed

Before you go

Start thinking like a software engineer.

Learn to code and solve interview problems, one small, checked step at a time.

Free, no card needed. Or look around first

By continuing you agree to the Terms of Service and Privacy Policy.