Skip to main content

How to Copy Directory Recursively

β€’ 2 min read
bash

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

MethodSyntaxPreservesBest For
cp -rcp -r src/ dst/NothingSimple copy
cp -rpcp -rp src/ dst/AttributesBackup/archive
rsync -avrsync -av src/ dst/AllNetwork sync
tar + extractComplexAllPortability
find + cpioComplexAllAdvanced

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

OptionPurpose
-rCopy recursively
-vVerbose output
-iInteractive (ask before overwriting)
-pPreserve attributes (permissions, timestamps)
-fForce (overwrite without asking)
-aArchive 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

  1. Missing final slash - rsync source/ dest/ vs rsync source dest/
  2. Forgetting -r flag - won’t copy subdirectories
  3. Not quoting variables - fails with spaces in names
  4. Overwriting without backup - always backup important data first
  5. Ignoring permissions - use -p to preserve them

Performance Tips

  • Use cp -a for archive-style copies with all attributes
  • For large directories, rsync is more efficient
  • Copy during off-hours for production systems
  • Monitor disk space before copying large trees

Key Points

  • Use cp -r for basic recursive copying
  • Use cp -a to preserve all file attributes
  • Use rsync for advanced copying with exclusions
  • Always verify important backups after copying
  • Test with --dry-run before 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.