How to Copy Directory Recursively
Quick Answer: Copy Directory Recursively
To copy a directory and all its contents in Bash, use cp -r: cp -r source/ destination/. Add -p to preserve file attributes and timestamps: cp -rp source/ destination/. This is the standard and most reliable method.
Quick Comparison: Directory Copying Methods
| Method | Syntax | Preserves | Best For |
|---|---|---|---|
| cp -r | cp -r src/ dst/ | Nothing | Simple copy |
| cp -rp | cp -rp src/ dst/ | Attributes | Backup/archive |
| rsync -av | rsync -av src/ dst/ | All | Network sync |
| tar + extract | Complex | All | Portability |
| find + cpio | Complex | All | Advanced |
Bottom line: Use cp -rp for standard recursive copying with attributes preserved.
Copy entire directory trees with all subdirectories and files in Bash. This guide covers basic copying, handling special files, and excluding specific directories.
Basic Recursive Copy
cp -r source_directory destination_directory
This copies the entire directory tree including all files and subdirectories.
Simple Example
# Copy a project directory
cp -r ~/projects/myapp ~/backup/myapp
# Verify the copy
ls -la ~/backup/myapp
Copy with Verbose Output
# Show each file being copied
cp -rv source_dir destination_dir
# Example output:
# 'source_dir/file1.txt' -> 'destination_dir/file1.txt'
# 'source_dir/subdir/file2.txt' -> 'destination_dir/subdir/file2.txt'
Copy to Existing Directory
# When destination exists, source becomes subdirectory
cp -r source_dir /path/to/existing_dir
# Results in: /path/to/existing_dir/source_dir/
Useful Options for Recursive Copy
| Option | Purpose |
|---|---|
-r | Copy recursively |
-v | Verbose output |
-i | Interactive (ask before overwriting) |
-p | Preserve attributes (permissions, timestamps) |
-f | Force (overwrite without asking) |
-a | Archive mode (preserves everything) |
Copy Preserving All Attributes
#!/bin/bash
# Archive mode: preserves permissions, ownership, timestamps
cp -av /home/user/project /backup/project
# Useful for backups where you want exact copy
Copy with Exclusions
# Using cp with find to skip certain files
find source_dir -type f ! -name "*.log" ! -name "*.tmp" -exec cp -r {} dest_dir \;
# Better approach with rsync:
rsync -av --exclude='*.log' --exclude='*.tmp' source_dir/ dest_dir/
Practical Example: Backup Project Directory
#!/bin/bash
# File: backup_project.sh
SOURCE_DIR="$1"
BACKUP_DIR="$2"
if [ -z "$SOURCE_DIR" ] || [ -z "$BACKUP_DIR" ]; then
echo "Usage: $0 <source_dir> <backup_dir>"
exit 1
fi
if [ ! -d "$SOURCE_DIR" ]; then
echo "ERROR: Source directory not found: $SOURCE_DIR"
exit 1
fi
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
# Get source directory name
source_name=$(basename "$SOURCE_DIR")
# Create timestamped backup
timestamp=$(date +%Y%m%d_%H%M%S)
backup_path="$BACKUP_DIR/${source_name}_${timestamp}"
# Copy with verbose output
echo "Starting backup..."
cp -av "$SOURCE_DIR" "$backup_path"
if [ $? -eq 0 ]; then
echo "Backup completed successfully: $backup_path"
# Show size
size=$(du -sh "$backup_path" | cut -f1)
echo "Backup size: $size"
else
echo "ERROR: Backup failed"
exit 1
fi
Usage:
$ chmod +x backup_project.sh
$ ./backup_project.sh ~/myapp ~/backups
Starting backup...
'~/myapp/file1.txt' -> '~/backups/myapp_20260221_143022/file1.txt'
...
Backup completed successfully: ~/backups/myapp_20260221_143022
Backup size: 245M
Copy Multiple Directories
#!/bin/bash
# Copy multiple source directories to destination
sources=("dir1" "dir2" "dir3")
destination="/backup"
for source in "${sources[@]}"; do
if [ -d "$source" ]; then
cp -rv "$source" "$destination/"
echo "Copied: $source"
else
echo "WARNING: Directory not found: $source"
fi
done
Copy Excluding Hidden Files
#!/bin/bash
# Copy directory excluding .git, .node_modules, etc.
SOURCE="$1"
DEST="$2"
if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
echo "Usage: $0 <source> <destination>"
exit 1
fi
mkdir -p "$DEST"
# Copy with exclusions using rsync (more efficient)
rsync -av \
--exclude='.git' \
--exclude='node_modules' \
--exclude='*.log' \
--exclude='.env' \
"$SOURCE/" "$DEST/"
echo "Copy completed"
Verify Copy Completeness
#!/bin/bash
# Compare source and destination
SOURCE="$1"
DEST="$2"
if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
echo "Usage: $0 <source> <destination>"
exit 1
fi
# Count files in source
source_count=$(find "$SOURCE" -type f | wc -l)
# Count files in destination
dest_count=$(find "$DEST" -type f | wc -l)
echo "Source files: $source_count"
echo "Destination files: $dest_count"
if [ "$source_count" -eq "$dest_count" ]; then
echo "β Copy verified: all files present"
else
echo "β MISMATCH: File count differs"
exit 1
fi
Rsync for Advanced Copying
#!/bin/bash
# rsync is better for large directories and updates
# -a: archive mode
# -v: verbose
# -z: compress during transfer
# --delete: remove files in dest that aren't in source
rsync -avz --delete /source/dir/ /destination/dir/
# Dry run first (doesn't actually copy)
rsync -avz --delete --dry-run /source/dir/ /destination/dir/
Common Mistakes
- Missing final slash -
rsync source/ dest/vsrsync source dest/ - Forgetting -r flag - wonβt copy subdirectories
- Not quoting variables - fails with spaces in names
- Overwriting without backup - always backup important data first
- Ignoring permissions - use
-pto preserve them
Performance Tips
- Use
cp -afor archive-style copies with all attributes - For large directories,
rsyncis more efficient - Copy during off-hours for production systems
- Monitor disk space before copying large trees
Key Points
- Use
cp -rfor basic recursive copying - Use
cp -ato preserve all file attributes - Use
rsyncfor advanced copying with exclusions - Always verify important backups after copying
- Test with
--dry-runbefore actual operations
Summary
Recursive directory copying is essential for backups and migrations. The cp -r command handles basic needs, while rsync provides more control for complex scenarios. Always verify your copies, especially for critical data.