Skip to content

Instantly share code, notes, and snippets.

@YossiCohen
Last active August 20, 2025 22:00
Show Gist options
  • Save YossiCohen/8fed321cb1cd294ca49428c3cb904985 to your computer and use it in GitHub Desktop.
Save YossiCohen/8fed321cb1cd294ca49428c3cb904985 to your computer and use it in GitHub Desktop.
PhotoRec Organizer for recovered files

🗂️ PhotoRec Organizer

Transform the chaos of recovered files into beautiful, organized perfection.

🚀 What is this?

PhotoRec is amazing at recovering lost files, but terrible at organizing them. You end up with thousands of files scattered across numbered directories with cryptic names. PhotoRec Organizer fixes that mess by creating a beautiful, logical file structure that makes sense. There are 2 versions - v1 and v2. V1 output folder structure: ORDER/[Extension]/[Size-Bucket]/[rec-folder-number][filename] V2 output folder structure: ORDER/[Extension]/[Year-Month]/[Size-Bucket]/[rec-folder-number][filename]

Before 😵

recup_dir.1/
├── f001234.jpg
├── f001235.pdf
├── f001236.mp4
└── ...
recup_dir.2/
├── f002456.png
└── ...

After 🎉

ORDER/
├── jpg/
│   ├── 2015-09/
│   │   ├── SMALL/
│   │   │   └── f001234.jpg
│   │   └── MEDIUM/
│   │       └── f001255.jpg
│   └── 2016-10/
│       └── LARGE/
│           └── f0012312.jpg
├── pdf/
│   └── 2024-03/
│       └── MEDIUM/
│           └── r0012224.pdf
└── mp4/
    └── 2023-12/
        └── HUGE/
            └── f006234.mp4

✨ Features

  • 🏷️ Smart File Naming: Files prefixed with recovery directory number
  • 📅 Time-Based Organization: Automatically sorts by year-month from file timestamps
  • 📏 Intelligent Size Buckets: TINY → SMALL → MEDIUM → LARGE → HUGE
  • 🔍 Multi-Platform: Works on whatever runs BASH
  • Fast Processing: Handles thousands of files efficiently
  • 🛡️ Conflict Resolution: Handles duplicate filenames (due to the naming prefix)
  • 📊 Progress Tracking: Real-time progress updates and statistics (buggy a bit :-| )
  • ⏯️ Allows Resume: The script is moving the files so you can stop it anytime and resume after

🎯 Size Buckets

Bucket Size Range Typical Files
TINY 0 - 100KB Thumbnails, text files, configs
SMALL 100KB - 1MB Documents, compressed images
MEDIUM 1MB - 10MB Photos, presentations
LARGE 10MB - 50MB High-res photos, short videos
HUGE 50MB+ Long videos, archives, disk images

🚀 Usage

Before running this make a backup of your recovery - the script is MOVING the files, so it will be good to run it over a backup. There is no warranty of any kind of using this script - I just can tell you that it did a great job for me

./photorec_organizer.sh /path/to/your/photorec/recovery/folder

Example

./photorec_organizer.sh ~/Desktop/photorec_recovery

The script will:

  1. Scan all recup_dir.* folders in your source directory
  2. Create an ORDER directory with organized files
  3. Show progress as it processes your files
  4. Display a summary of what was organized

📁 Directory Structure

The script creates a logical hierarchy:

ORDER/
└── [FILE_EXTENSION]/
    └── [YEAR-MONTH]/
        └── [SIZE_BUCKET]/
            └── [RECUP_NUMBER]_[ORIGINAL_FILENAME]

Examples:

  • ORDER/jpg/2024-03/MEDIUM/5_IMG_1234.jpg
  • ORDER/pdf/2023-11/SMALL/12_document.pdf
  • ORDER/mp4/UNKNOWN/HUGE/3_video.mp4 (for files without detectable dates)

🧠 Smart Date Detection

The script intelligently extracts dates from:

  1. File modification timestamps (primary method)
  2. Fallback to "UNKNOWN" for undatable files

📈 Sample Output

Starting PhotoRec file organization...
Source directory: /home/user/recovery
Target directory: ORDER

Found 2,847 files to process

Processing recup_dir.1 (number: 1)...
Processing recup_dir.2 (number: 2)...
...
  Processed 2,800/2,847 files...

File organization complete!
Processed: 2,847 files
Files organized in: ORDER

Size bucket distribution:
  TINY: Found in 15 different extension/date combinations
  SMALL: Found in 28 different extension/date combinations
  MEDIUM: Found in 42 different extension/date combinations
  LARGE: Found in 18 different extension/date combinations
  HUGE: Found in 8 different extension/date combinations

🔧 Requirements

  • Bash shell (4.0+)
  • Linux or macOS
  • Standard Unix utilities: find, stat, date, cut

🤝 Contributing

Found a bug? Have an idea? Pull requests and issues are welcome!

📝 License

This project is licensed under the MIT License - feel free to use, modify, and distribute.

💡 Pro Tips

  • Run on a copy first: Test with a small subset of files before processing everything
  • Check timestamps: Files might be organized by recovery date, not original creation date
  • Large datasets: For huge recoveries (10k+ files), consider running overnight
  • Multiple runs: Safe to run multiple times - existing files won't be overwritten

🙏 Acknowledgments

  • Built for the awesome PhotoRec recovery tool
  • Inspired by everyone who's ever faced the "organized chaos" of file recovery
  • Written (also this readme) with the help of https://claude.ai/

Built with ❤️ for data recovery heroes everywhere

#!/bin/bash
# PhotoRec File Organizer Script
# Organizes recovered files from photorec into structured folders
# ORDER/[Extension]/[Size-Bucket]/[rec-folder-number]_[filename]
# Check if source directory is provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <photorec_source_directory>"
echo "Example: $0 /path/to/photorec/recovery"
exit 1
fi
SOURCE_DIR="$1"
ORDER_DIR="ORDER"
# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
echo "Error: Source directory '$SOURCE_DIR' does not exist."
exit 1
fi
# Create ORDER directory if it doesn't exist
mkdir -p "$ORDER_DIR"
echo "Starting PhotoRec file organization..."
echo "Source directory: $SOURCE_DIR"
echo "Target directory: $ORDER_DIR"
echo ""
# Counter for processed files
processed_files=0
total_files=0
# First, count total files for progress indication
echo "Counting files..."
total_files=$(find "$SOURCE_DIR" -type f -name "*" | wc -l)
echo "Found $total_files files to process"
echo ""
# Function to get size bucket based on file size in bytes
get_size_bucket() {
local size=$1
local size_mb=$((size / 1024 / 1024))
local size_kb=$((size / 1024))
# SMALL: 0-0.5MB (0-512KB)
if [ $size_kb -le 512 ]; then
echo "SMALL"
# MED: 0.5MB-10MB
elif [ $size_mb -le 10 ]; then
echo "MED"
# BIG: 10MB+
else
echo "BIG"
fi
}
# Function to get file extension (lowercase)
get_extension() {
local filename="$1"
local ext="${filename##*.}"
# If no extension found, return "NO_EXT"
if [ "$ext" = "$filename" ]; then
echo "NO_EXT"
else
echo "${ext,,}" # Convert to lowercase
fi
}
# Process each recup_dir.* directory
for recup_dir in "$SOURCE_DIR"/recup_dir.*; do
if [ ! -d "$recup_dir" ]; then
continue
fi
# Extract the number from recup_dir.X
recup_number=$(basename "$recup_dir" | sed 's/recup_dir\.//')
echo "Processing $recup_dir (number: $recup_number)..."
# Process each file in the recup directory
find "$recup_dir" -type f | while read -r filepath; do
filename=$(basename "$filepath")
# Skip if it's a directory or empty
if [ ! -f "$filepath" ]; then
continue
fi
# Get file size in bytes
file_size=$(stat -f%z "$filepath" 2>/dev/null || stat -c%s "$filepath" 2>/dev/null)
# Get file extension
extension=$(get_extension "$filename")
# Get size bucket
size_bucket=$(get_size_bucket "$file_size")
# Create new filename with recup number prefix
new_filename="${recup_number}_${filename}"
# Create target directory structure
target_dir="$ORDER_DIR/$extension/$size_bucket"
mkdir -p "$target_dir"
# Target file path
target_path="$target_dir/$new_filename"
# Handle filename conflicts by adding a counter
counter=1
original_target="$target_path"
while [ -e "$target_path" ]; do
name_without_ext="${new_filename%.*}"
ext_part="${new_filename##*.}"
if [ "$ext_part" = "$new_filename" ]; then
# No extension
target_path="${original_target}_${counter}"
else
# Has extension
target_path="$target_dir/${name_without_ext}_${counter}.${ext_part}"
fi
counter=$((counter + 1))
done
# Move the file
if mv "$filepath" "$target_path"; then
processed_files=$((processed_files + 1))
# Show progress every 100 files
if [ $((processed_files % 100)) -eq 0 ]; then
echo " Processed $processed_files/$total_files files..."
fi
# Verbose output for first few files to show what's happening
if [ $processed_files -le 5 ]; then
size_kb=$((file_size / 1024))
echo " Moved: $filename -> $extension/$size_bucket/$new_filename (${size_kb}KB)"
fi
else
echo " Error moving file: $filepath"
fi
done
done
echo ""
echo "File organization complete!"
echo "Processed: $processed_files files"
echo "Files organized in: $ORDER_DIR"
echo ""
echo "Directory structure created:"
find "$ORDER_DIR" -type d | sort
#!/bin/bash
# PhotoRec File Organizer Script
# Organizes recovered files from photorec into structured folders by extension/date/size
# Structure: ORDER/[EXTENSION]/[YEAR-MONTH]/[SIZE_BUCKET]/
# Check if source directory is provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <photorec_source_directory>"
echo "Example: $0 /path/to/photorec/recovery"
exit 1
fi
SOURCE_DIR="$1"
ORDER_DIR="ORDER"
# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
echo "Error: Source directory '$SOURCE_DIR' does not exist."
exit 1
fi
# Create ORDER directory if it doesn't exist
mkdir -p "$ORDER_DIR"
echo "Starting PhotoRec file organization..."
echo "Source directory: $SOURCE_DIR"
echo "Target directory: $ORDER_DIR"
echo ""
# Counter for processed files
processed_files=0
total_files=0
# First, count total files for progress indication
echo "Counting files..."
total_files=$(find "$SOURCE_DIR" -type f -name "*" | wc -l)
echo "Found $total_files files to process"
echo ""
# Function to get size bucket based on file size in bytes
get_size_bucket() {
local size=$1
local size_kb=$((size / 1024))
# TINY: 0-100KB
if [ $size_kb -le 100 ]; then
echo "TINY"
# SMALL: 100KB-1MB (1024KB)
elif [ $size_kb -le 1024 ]; then
echo "SMALL"
# MEDIUM: 1MB-10MB
elif [ $size_kb -le 10240 ]; then
echo "MEDIUM"
# LARGE: 10MB-50MB
elif [ $size_kb -le 51200 ]; then
echo "LARGE"
# HUGE: 50MB+
else
echo "HUGE"
fi
}
# Function to get file extension (lowercase)
get_extension() {
local filename="$1"
local ext="${filename##*.}"
# If no extension found, return "NO_EXT"
if [ "$ext" = "$filename" ]; then
echo "NO_EXT"
else
echo "${ext,,}" # Convert to lowercase
fi
}
# Function to get year-month from file timestamp
get_year_month() {
local filepath="$1"
local timestamp=""
# Detect OS and use appropriate stat command
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
timestamp=$(stat -f "%Sm" -t "%Y-%m" "$filepath" 2>/dev/null)
else
# Linux (including Ubuntu)
timestamp=$(stat -c "%y" "$filepath" 2>/dev/null | cut -d' ' -f1 | cut -d'-' -f1,2)
fi
# Validate timestamp format (YYYY-MM)
if [ -n "$timestamp" ] && [[ "$timestamp" =~ ^[0-9]{4}-[0-9]{2}$ ]]; then
echo "$timestamp"
return
fi
# Alternative Linux method using date command
if command -v stat >/dev/null 2>&1; then
local epoch_time=$(stat -c "%Y" "$filepath" 2>/dev/null)
if [ -n "$epoch_time" ] && [ "$epoch_time" -gt 0 ]; then
timestamp=$(date -d "@$epoch_time" "+%Y-%m" 2>/dev/null)
if [ -n "$timestamp" ] && [[ "$timestamp" =~ ^[0-9]{4}-[0-9]{2}$ ]]; then
echo "$timestamp"
return
fi
fi
fi
# Fallback: try to extract date from filename if it contains date patterns
local filename=$(basename "$filepath")
# Look for patterns like: 20240315, 2024-03-15, 2024_03_15, IMG_20240315, etc.
if [[ "$filename" =~ ([0-9]{4})[_-]?([0-9]{2})[_-]?([0-9]{2}) ]]; then
local year="${BASH_REMATCH[1]}"
local month="${BASH_REMATCH[2]}"
# Basic validation
if [ "$year" -ge 1990 ] && [ "$year" -le 2030 ] && [ "$month" -ge 1 ] && [ "$month" -le 12 ]; then
echo "$year-$month"
return
fi
fi
# Final fallback: use UNKNOWN
echo "UNKNOWN"
}
# Process each recup_dir.* directory
for recup_dir in "$SOURCE_DIR"/recup_dir.*; do
if [ ! -d "$recup_dir" ]; then
continue
fi
# Extract the number from recup_dir.X
recup_number=$(basename "$recup_dir" | sed 's/recup_dir\.//')
echo "Processing $recup_dir (number: $recup_number)..."
# Process each file in the recup directory
find "$recup_dir" -type f | while read -r filepath; do
filename=$(basename "$filepath")
# Skip if it's a directory or empty
if [ ! -f "$filepath" ]; then
continue
fi
# Get file size in bytes
file_size=$(stat -f%z "$filepath" 2>/dev/null || stat -c%s "$filepath" 2>/dev/null)
# Get file extension
extension=$(get_extension "$filename")
# Get size bucket
size_bucket=$(get_size_bucket "$file_size")
# Get year-month
year_month=$(get_year_month "$filepath")
# Create new filename with recup number prefix
new_filename="${recup_number}_${filename}"
# Create target directory structure: ORDER/EXTENSION/YEAR-MONTH/SIZE_BUCKET/
target_dir="$ORDER_DIR/$extension/$year_month/$size_bucket"
mkdir -p "$target_dir"
# Target file path
target_path="$target_dir/$new_filename"
# Handle filename conflicts by adding a counter
counter=1
original_target="$target_path"
while [ -e "$target_path" ]; do
name_without_ext="${new_filename%.*}"
ext_part="${new_filename##*.}"
if [ "$ext_part" = "$new_filename" ]; then
# No extension
target_path="${original_target}_${counter}"
else
# Has extension
target_path="$target_dir/${name_without_ext}_${counter}.${ext_part}"
fi
counter=$((counter + 1))
done
# Move the file
if mv "$filepath" "$target_path"; then
processed_files=$((processed_files + 1))
# Show progress every 100 files
if [ $((processed_files % 100)) -eq 0 ]; then
echo " Processed $processed_files/$total_files files..."
fi
# Verbose output for first few files to show what's happening
if [ $processed_files -le 5 ]; then
size_kb=$((file_size / 1024))
echo " Moved: $filename -> $extension/$year_month/$size_bucket/$new_filename (${size_kb}KB)"
fi
else
echo " Error moving file: $filepath"
fi
done
done
echo ""
echo "File organization complete!"
echo "Processed: $processed_files files"
echo "Files organized in: $ORDER_DIR"
echo ""
echo "Directory structure summary:"
echo "Format: ORDER/[EXTENSION]/[YEAR-MONTH]/[SIZE_BUCKET]/"
echo ""
echo "Created directories:"
find "$ORDER_DIR" -type d | head -20 | sort
if [ $(find "$ORDER_DIR" -type d | wc -l) -gt 20 ]; then
echo "... (and $(($(find "$ORDER_DIR" -type d | wc -l) - 20)) more directories)"
fi
echo ""
echo "Size bucket distribution:"
for bucket in TINY SMALL MEDIUM LARGE HUGE; do
count=$(find "$ORDER_DIR" -type d -name "$bucket" | wc -l)
if [ $count -gt 0 ]; then
echo " $bucket: Found in $count different extension/date combinations"
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment