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 fullPath.glob("*.py")matches in that folder only.rglob("*.py")searches the folder and all subfolders. It is the same asglob("**/*.py").is_file()andis_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.
