Python Central HubSign in

Automation and Scripting / Phase 1: OS & File System Automation

Managing Paths with pathlib

Use pathlib.Path for readable, cross-platform path handling.

Why pathlib

pathlib gives you:

  • operator-free path joining
  • clear methods (exists, is_file, mkdir, read_text)
  • cross-platform behavior

Before pathlib, paths were plain strings joined with os.path.join or, worse, with + "/" +. That breaks on Windows, where the separator is a backslash, and it makes it easy to build a path that does not point anywhere. A Path object knows the rules of the operating system it runs on. You join parts with /, and you ask the object questions instead of slicing strings.

Automation scripts spend most of their time on paths, so this is the single most useful habit to learn early.

Common patterns

Python
from pathlib import Path
 
base = Path.home() / "Downloads"
print(base)
 
for f in base.iterdir():
    if f.is_file():
        print(f.name, f.suffix)

Reading and writing

Python
from pathlib import Path
 
p = Path("hello.txt")
p.write_text("hello\n", encoding="utf-8")
print(p.read_text(encoding="utf-8"))

๐Ÿงช Try It Yourself

Exercise 1 โ€“ List Files with os.listdir

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 2 โ€“ Join Paths with os.path.join

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 3 โ€“ Write and Read a File

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Taking a path apart

A path has named parts. You rarely need string slicing.

Python
from pathlib import PurePosixPath
 
p = PurePosixPath("/home/ana/reports/sales.2026.csv")
print(p.name)     # sales.2026.csv
print(p.stem)     # sales.2026
print(p.suffix)   # .csv
print(p.parent)   # /home/ana/reports
print(p.with_suffix(".json"))       # /home/ana/reports/sales.2026.json
print(p.with_name("costs.csv"))     # /home/ana/reports/costs.csv

PurePosixPath only manipulates text and never touches the disk, so it is safe to experiment with anywhere. In real scripts you use Path, which adds the methods that do touch the disk. p.parts gives you a tuple of every component.

Useful methods

TaskMethod
Does it exist?p.exists(), p.is_file(), p.is_dir()
Create a folderp.mkdir(parents=True, exist_ok=True)
List a folderp.iterdir()
Find files by patternp.glob("*.txt"), p.rglob("*.txt") (recursive)
Absolute pathp.resolve()
Rename or movep.rename(new_path)
Delete a filep.unlink()
Size and timep.stat().st_size, p.stat().st_mtime

Common mistakes

  • Forgetting that / joins paths. Path("a") / "b" works. Path("a") + "b" raises a TypeError.
  • Using a relative path and assuming the working directory. Path("data.csv") is relative to wherever the script was launched, which is different under cron or Task Scheduler. Build paths from Path(__file__).parent when the file sits next to the script.
  • Forgetting encoding. read_text() without encoding="utf-8" uses the system default, which differs between machines.
  • An absolute right-hand side. Path("a") / "/etc" gives /etc, because an absolute right side replaces the left.
  • Calling unlink() or rename() without checking. rename can overwrite silently on some systems. Check exists() first.

Adapting it

To process every CSV in a downloads folder, combine what you have seen: for f in (Path.home() / "Downloads").glob("*.csv"), then use f.stem and f.with_suffix() to build output names. The same three or four calls cover most file-handling scripts.

Was this page helpful?

Let us know how we did