Manage Directories with shutil and glob
Learn how to manage directories in Python using shutil and glob — covering creation, copying, moving, deleting, and pattern matching files for efficient file system operations.
Focus: manage directories with shutil and glob
You’ve built Python scripts that create files, write data, and maybe even clean up after themselves. But as soon as you need to organize dozens of reports, move backups into dated folders, or delete stale temp directories, looping over os.listdir() and building manual paths gets brittle, verbose, and error-prone. The real pain is not reading a single file—it’s managing entire directory trees at scale. That’s where Python’s shutil and glob modules step in, giving you high-level operations (copy, move, delete a whole folder) and pattern-based file discovery, all without reinventing the wheel.
The problem this lesson solves
When your project outgrows a handful of files, manual directory management becomes a nightmare. Imagine you have a data/ folder that accumulates weekly exports. You need to:
- Archive last week’s folder into a
backups/directory. - Delete any folder whose name starts with
tmp_. - Find all
.csvfiles nested three levels deep.
Using only os.path and shutil.rmtree is possible, but cobbling together recursion, pattern matching, and error handling for every script is tedious and fragile. The core problem is that Python’s low-level os module doesn’t offer modern, ergonomic tools for batch directory operations or file pattern matching. Without shutil and glob, you’d write more code, introduce more bugs, and spend time debugging path logic instead of solving the actual problem.
Core concept / mental model
Think of shutil as your file & folder assistant — it knows how to copy, move, rename, and delete entire directories in one call. glob is your pattern-finding scout — it searches for files and directories whose names match a wildcard pattern (*.csv, **/*.json), like a simplified regular expression for file paths.
Pro tip:
shutilhandles the heavy lifting of recursive operations, whereasglobreplaces many manualos.listdir+fnmatchloops. Together they form the essential toolkit for file wrangling in Python.
Key terms
- shutil — Short for “shell utilities.” Functions like
shutil.copy(),shutil.copytree(),shutil.move(),shutil.rmtree(), andshutil.make_archive(). - glob — Pattern matching using
*,?, and[], plus**for recursive matching (Python 3.5+). - Working directory — The folder your script considers “home”; affected by
os.chdir()or relative paths.
How it works step by step
1. Import the modules
import shutil
import glob
Both are part of Python’s standard library — no pip install needed.
2. Working with shutil for directories
The table below summarizes the most common shutil directory functions:
| Function | Action | Key behavior |
|---|---|---|
shutil.copytree(src, dst) |
Copy entire directory tree | Creates dst; raises error if dst exists |
shutil.move(src, dst) |
Move or rename a directory | Works across filesystems |
shutil.rmtree(path) |
Delete a directory and all contents | Irreversible — use with caution |
shutil.make_archive(base_name, format, root_dir) |
Compress root_dir into a zip/tar archive |
Supports 'zip', 'tar', 'gztar', 'bztar', 'xztar' |
Warning:
shutil.rmtree()deletes without moving to a trash bin. Always test on non‑production data first.
3. Using glob to find directories (and files)
glob.glob(pathname, recursive=False) returns a list of path strings matching the given pattern. Enable recursive searching with recursive=True and **.
| Pattern | Matches | Example |
|---|---|---|
* |
Any file/folder name (except hidden) | glob.glob('*.py') → all .py files |
? |
Single character | glob.glob('data_?.csv') → data_1.csv but not data_12.csv |
[abc] |
Character class | glob.glob('report_[0-9].xlsx') → report_5.xlsx |
** |
Recursive directory traversal | glob.glob('logs/**/*.log', recursive=True) |
Hands-on walkthrough
Let’s build a practical example: we’ll create a simulated project structure, then use shutil and glob to manage it.
Setup: create test directories and files
import os
import shutil
# Create a demo workspace
os.makedirs('my_project/data/raw', exist_ok=True)
os.makedirs('my_project/data/processed', exist_ok=True)
os.makedirs('my_project/backups', exist_ok=True)
os.makedirs('my_project/temp/test', exist_ok=True)
# Create some dummy files
for folder in ['data/raw', 'data/processed', 'temp/test']:
for i in range(3):
open(f'my_project/{folder}/file_{i}.txt', 'w').close()
print("Workspace created.")
Expected output: No errors; the directory tree is ready.
Copy an entire directory tree
import shutil
# Copy the 'data' folder into 'backups'
shutil.copytree('my_project/data', 'my_project/backups/data_backup')
print("Data backed up.")
Check:
backups/data_backupnow containsraw/andprocessed/with all files.
Delete all temp directories using glob + shutil
import glob
import shutil
for temp_dir in glob.glob('my_project/**/temp*', recursive=True):
if os.path.isdir(temp_dir):
shutil.rmtree(temp_dir)
print(f"Removed {temp_dir}")
Expected output:
Removed my_project/temp
Move one directory into another
shutil.move('my_project/data/processed', 'my_project/backups/processed_backup')
print("Moved processed folder.")
Now backups/ contains both data_backup and processed_backup.
Compare options / when to choose what
| Task | os module |
shutil / glob |
Why prefer the latter |
|---|---|---|---|
| Create a directory | os.mkdir() / os.makedirs() |
os.makedirs(exist_ok=True) |
Both are fine; makedirs is one-liner for nested dirs |
| Copy a single file | os.system('cp ...') |
shutil.copy2() |
Preserves metadata; cross‑platform |
| Copy a whole folder | Recursive os code |
shutil.copytree() |
Avoids 20+ lines of recursion |
| Delete a folder tree | os.rmdir() (only empty) |
shutil.rmtree() |
Works on non‑empty dirs |
Find .txt files in subfolders |
os.walk() + fnmatch |
glob.glob('**/*.txt', recursive=True) |
One call, less boilerplate |
Rule of thumb: If it involves a whole directory tree (copy, move, delete) or pattern matching over many paths, reach for
shutilandglob. If you’re handling a single file or need fine‑grained control (like filtering by file size or modification date),os.pathandos.walkmight still be better.
Troubleshooting & edge cases
“Destination already exists” errors with shutil.copytree
shutil.copytreeraisesFileExistsError` if the target folder already exists. Workaround:
if not os.path.exists('backups/new_folder'):
shutil.copytree('source', 'backups/new_folder')
else:
print("Target already exists. Use `shutil.rmtree()` first or rename.")
Symlink handling
shutil.copytree by default copies the content of symlinks (not the symlinks themselves). Use symlinks=True to preserve them:
shutil.copytree('src', 'dst', symlinks=True)
glob with hidden files
Files starting with . are not matched by default. Use glob.glob('.*') or glob.glob('**/.*', recursive=True) to include them.
Large directory deletion is slow
shutil.rmtree deletes each file and folder one call at a time. For huge trees with millions of files, consider using os.system('rm -rf dir') (Linux/macOS) or third‑party libraries like pyfilesystem2 for performance. But for most projects, shutil is perfectly adequate.
Path is a file, not a directory
shutil.copytree, shutil.rmtree, and shutil.move on a file (not a directory) will raise NotADirectoryError. Always check with os.path.isdir().
if os.path.isdir('my_target'):
shutil.rmtree('my_target')
else:
os.remove('my_target')
What you learned & what's next
You now know how to manage directories with shutil and glob.
shutilgives you high‑level commands to copy, move, rename, and delete whole directory trees — no recursion boilerplate.globlets you find files and directories by pattern, including recursive traversal with**.- You can combine them: use
globto locate directories, thenshutil.rmtree()orshutil.move()to act on them.
You’ve met the lesson’s objectives:
- ✅ Explain the core idea behind managing directories with
shutilandglob. - ✅ Complete a practical exercise — creating, copying, moving, and deleting folders.
Next up: You’ll learn how to Work with zip and tar archives — compressing and extracting bundles of files, a natural follow‑up to organizing directories. This will complete your file‑management toolkit.
Practice recap
Create a small script that: (1) uses glob to find all directories starting with tmp_ inside a sandbox/ folder, (2) prints their names and sizes (total file count), then (3) moves them to an archive/ folder. Run the script, then verify the directories moved. For a challenge, use shutil.make_archive() to zip the archive/ folder afterward.
Common mistakes
- Using
shutil.rmtree()on a file path — always check withos.path.isdir()first. - Forgetting to set
recursive=Trueinglob.glob()when using**— it defaults toFalseand matches nothing. - Calling
shutil.copytree()when the destination already exists — it raisesFileExistsError. Either remove the dest first or use a different name. - Assuming
globreturns paths in sorted order — it does, but only lexicographically, which may not match human sorting (e.g.,file9.txtbeforefile10.txt).
Variations
- Use
pathlib.Path().glob()instead ofglob.glob()— it returnsPathobjects and supports method chaining (e.g.,Path('src').glob('*.txt')). - Use
os.walk()when you need detailed per‑file metadata (size, mtime) alongside path discovery —globdoesn’t provide metadata. - Combine
shutilwithtempfile.TemporaryDirectory()for safe, self‑cleaning temporary workspaces.
Real-world use cases
- Backup automation: copy a project’s
data/folder to a timestamped archive directory daily usingshutil.copytree(). - Log rotation: use
glob('logs/**/*.log')to find all log files, thenshutil.move()the oldest ones to alogs/archive/folder. - CI/CD cleanup: after a build, delete all
node_modulesor__pycache__directories recursively usingglob('**/node_modules')+shutil.rmtree().
Key takeaways
shutilprovides high‑level directory operations:copytree(),move(),rmtree(), andmake_archive().globmatches file and directory paths using wildcards (*,?,[ ]) and recursive**withrecursive=True.- Always check if a path is a directory (
os.path.isdir()) before callingshutil.rmtree()orcopytree(). - Combine
globandshutilfor batch operations: find → then copy/move/delete.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.