shutil

import shutil
Added in v3.2 · Updated March 13, 2026 · Modules
stdlib filesystem files copy move archive compression

The shutil module provides high-level file operations that go beyond what the os module offers. It handles tasks like copying entire directory trees, moving files across filesystems, removing directories recursively, and creating or extracting archives in various formats. If you’re writing scripts that manipulate files, shutil is almost certainly part of the solution.

Most shutil functions work with path-like objects (strings or paths from pathlib), making them compatible with modern Python code.

Copying Files

copy()

The shutil.copy() function copies a file’s contents and permission mode to another file or into a directory.

shutil.copy(src, dst, follow_symlinks=True)

Parameters:

ParameterTypeDefaultDescription
srcpath-likeSource file path
dstpath-likeDestination file or directory
follow_symlinksboolTrueIf False and src is a symlink, create a symlink instead of copying the file

Returns: The path to the newly created file.

Examples:

import shutil

# Copy a file
shutil.copy('source.txt', 'destination.txt')

# Copy into a directory (filename is derived from src)
shutil.copy('file.txt', 'myfolder/')

# Copy without following symlinks
shutil.copy('link.txt', 'output.txt', follow_symlinks=False)

This preserves the file’s permission mode but not metadata like creation time. For preserving all metadata, use shutil.copy2() instead.

Gotchas:

  • If dst is a directory that already contains a file with the same name, it gets replaced silently.
  • shutil.copy() cannot copy special files like device nodes or named pipes.

Errors: Raises OSError if the source file cannot be read, the destination cannot be written, or if the destination is a directory and the filesystem doesn’t support replacement.


copytree()

This function recursively copies an entire directory tree.

shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=shutil.copy2, 
                ignore_dangling_symlinks=False, dirs_exist_ok=False)

Parameters:

ParameterTypeDefaultDescription
srcpath-likeSource directory path
dstpath-likeDestination directory path
symlinksboolFalseIf True, preserve symlinks as symlinks; otherwise copy file contents
ignorecallableNoneCallback to ignore files/directories during copy
copy_functioncallableshutil.copy2Function to use for copying files
ignore_dangling_symlinksboolFalseIgnore dangling symlinks when symlinks=False
dirs_exist_okboolFalseIf True, allow destination to already exist

Returns: The destination directory path.

Examples:

import shutil

# Basic directory copy
shutil.copytree('source_folder', 'destination_folder')

# Copy ignoring certain patterns
import glob
shutil.copytree('src', 'dst', ignore=glob.ignore_patterns('*.pyc', '__pycache__'))

# Allow destination to already exist (Python 3.8+)
shutil.copytree('src', 'dst', dirs_exist_ok=True)

The ignore parameter is handy for skipping files like cache directories or compiled files.

Errors: Raises OSError if directories cannot be created or files cannot be copied. Also raises shutil.Error when errors occur during the copy process — this exception contains a list of 3-tuples with (source, destination, exception) for each failed item.


Moving and Removing

move()

Moves a file or directory to another location.

shutil.move(src, dst, copy_function=shutil.copy2)

Parameters:

ParameterTypeDefaultDescription
srcpath-likeSource file or directory
dstpath-likeDestination path
copy_functioncallableshutil.copy2Function to use when copy is needed

Returns: The destination path.

Examples:

import shutil

# Move a file
shutil.move('file.txt', 'new_location.txt')

# Move a directory
shutil.move('myfolder/', 'new_folder/')

# Move into an existing directory
shutil.move('file.txt', 'existing_folder/')

The behavior with directories is slightly complex: if dst is an existing directory, src moves inside it. If dst exists but isn’t a directory, it may be overwritten depending on the OS.

Errors: Raises OSError if the source doesn’t exist, the destination cannot be written, or if both source and destination are on different filesystems and the copy fails.


rmtree()

Removes an entire directory tree. Use with caution.

shutil.rmtree(path, ignore_errors=False, onerror=None, *, dir_fd=None)

Parameters:

ParameterTypeDefaultDescription
pathpath-likeDirectory to remove
ignore_errorsboolFalseIgnore errors during removal
onerrorcallableNoneCallback for handling exceptions
dir_fdintNoneDirectory file descriptor

Examples:

import shutil

# Remove a directory tree
shutil.rmtree('old_folder')

# Ignore errors (won't raise exceptions)
shutil.rmtree('folder', ignore_errors=True)

# Custom error handling
def handle_error(func, path, exc_info):
    print(f"Failed to delete {path}: {exc_info[1]}")
    
shutil.rmtree('folder', onerror=handle_error)

Python 3.3+ includes symlink attack protection on platforms that support fd-based functions. Check shutil.rmtree.avoids_symlink_attacks to see if your platform is protected.

Errors: Raises OSError if the directory cannot be removed (unless ignore_errors=True). On Windows, may fail on read-only files unless handled with onerror.


Archiving

make_archive()

Creates compressed archive files in various formats.

shutil.make_archive(base_name, format, root_dir=None, base_dir=None, dry_run=False, 
                    owner=None, group=None, logger=None)

Parameters:

ParameterTypeDefaultDescription
base_namestrName of archive (without extension)
formatstrArchive format: “zip”, “tar”, “gztar”, “bztar”, “xztar”, “zstdtar”
root_dirpath-likeNoneRoot directory of archive (becomes archive root)
base_dirpath-likeNoneDirectory within root_dir to archive from
dry_runboolFalseIf True, don’t create archive but log operations
ownerstrNoneOwner name for tar archives
groupstrNoneGroup name for tar archives
loggerobjectNoneLogger compatible with PEP 282

Returns: The path to the created archive file.

Examples:

import shutil

# Create a zip archive
shutil.make_archive('mybackup', 'zip', root_dir='/path/to/backup')

# Create a tar.gz archive
shutil.make_archive('project', 'gztar', root_dir='./project')

# Archive a specific subdirectory
shutil.make_archive('data', 'tar', root_dir='./', base_dir='data/')

The available formats depend on which compression libraries are installed: zlib for zip/gztar, bz2 for bztar, lzma for xztar, and zstandard for zstdtar.

Errors: Raises OSError if the archive cannot be created, the format is not supported, or the root directory doesn’t exist.


unpack_archive()

Extracts archives of various formats automatically.

shutil.unpack_archive(filename, extract_dir=None, format=None, *, filter=None)

Parameters:

ParameterTypeDefaultDescription
filenamepath-likeArchive file path
extract_dirpath-likeNoneDirectory to extract to (default: current directory)
formatstrNoneForce a specific format (auto-detected if None)
filtercallableNoneFilter function for tar archives (Python 3.12+)

Examples:

import shutil

# Extract to current directory
shutil.unpack_archive('archive.zip')

# Extract to specific directory
shutil.unpack_archive('backup.tar.gz', extract_dir='./restored')

# Force format
shutil.unpack_archive('unknown_file', format='zip')

Python’s format detection is usually reliable, but you can override it with the format parameter if needed.

Errors: Raises shutil.ReadError if the archive cannot be read, the format is not recognized, or the extraction directory cannot be created.


System Utilities

which()

Finds an executable in the system PATH.

shutil.which(cmd, mode=os.F_OK, path=None)

Parameters:

ParameterTypeDefaultDescription
cmdstrCommand name to find
modeintos.F_OKPermission mask (F_OK: exists, X_OK: executable)
pathstrNoneCustom PATH string (uses PATH env var if None)

Returns: Full path to executable, or None if not found.

Examples:

import shutil
import os

# Find Python interpreter
python_path = shutil.which('python')
print(f"Found Python at: {python_path}")

# Find with specific permissions
exec_path = shutil.which('myapp', mode=os.X_OK)

# Use custom PATH
custom_path = shutil.which('gcc', path='/usr/bin:/usr/local/bin')

This is useful for building tools that need to locate external programs. On Windows, it handles the PATHEXT variable automatically, so searching for “python” finds “python.exe”.

Errors: Returns None if the command is not found in the PATH or doesn’t have the required permissions. Does not raise exceptions.


disk_usage()

Returns disk space statistics for a path.

shutil.disk_usage(path)

Parameters:

ParameterTypeDefaultDescription
pathpath-likePath to check (file or directory)

Returns: Named tuple with total, used, and free attributes (in bytes).

Examples:

import shutil

# Check disk usage of current directory
usage = shutil.disk_usage('.')
print(f"Total: {usage.total / (1024**3):.2f} GB")
print(f"Used: {usage.used / (1024**3):.2f} GB")
print(f"Free: {usage.free / (1024**3):.2f} GB")

# Check specific path
usage = shutil.disk_usage('/home')
print(f"Home directory: {usage.free / (1024**3):.2f} GB free")

On Unix, path must be within a mounted filesystem partition. On Windows, it can be any path.

Errors: Raises OSError if the path does not exist or is not accessible.


Common Patterns

Preserving metadata

Use shutil.copy2() instead of shutil.copy() when you need to preserve file metadata like timestamps:

import shutil

# copy2 preserves more metadata (like access/modify times)
shutil.copy2('source.txt', 'destination.txt')

Platform-specific fast copies

Since Python 3.8, shutil uses efficient kernel-level copy operations where available (sendfile on Linux, fcopyfile on macOS, CopyFileEx on Windows). This happens automatically and falls back silently if it fails.

Error handling for copytree

For shutil.copytree(), errors are collected into a shutil.Error exception with details about what failed:

import shutil

try:
    shutil.copytree('src', 'dst')
except shutil.Error as e:
    for src, dst, exc in e.args[0]:
        print(f"Failed to copy {src} to {dst}: {exc}")

See Also