How to Create a Bash File Recovery Tool

How to Create a Bash File Recovery Tool

How to Create a Bash File Recovery Tool

Every sysadmin has that one story — the accidental rm -rf, the overwritten config file, the deleted directory that nobody had a backup of. I have a few of those stories myself, and after the second time I lost something important, I decided to build a Bash-based file recovery tool that gives me a fighting chance to get files back before disaster strikes for real. In this article, I’ll walk you through exactly how I built it, why each piece matters, and how you can adapt it for your own systems.

I want to be upfront about something important: Bash alone cannot magically undelete a file that the filesystem has already overwritten. What we’re really building here is a combination of a safety net (so files are recoverable before they’re truly gone) and a recovery wrapper around existing low-level recovery utilities. That distinction matters, and I’ll explain it clearly as we go.

Understanding How File Deletion Actually Works

When you delete a file on Linux, the data usually isn’t wiped from disk immediately. The filesystem just marks the space as available for reuse. Until something else overwrites that space, recovery is often possible using tools like extundelete, testdisk, or photorec. Our Bash tool will do two things:

  1. Intercept deletions and move files to a “trash” location instead of deleting them outright (prevention).
  2. Wrap low-level recovery utilities in a friendly interface for genuine after-the-fact recovery.

Prerequisites

Part 1: Building a “Trash” Interceptor (Prevention Is Better Than Cure)

The single best recovery tool is the one that prevents permanent loss in the first place. Let’s build a safe-rm script that moves files to a trash folder instead of deleting them.

#!/usr/bin/env bash
set -euo pipefail

TRASH_DIR="$HOME/.local/share/bash-trash"
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")

mkdir -p "$TRASH_DIR"

if [[ $# -eq 0 ]]; then
    echo "Usage: saferm <file1> [file2] [file3] ..."
    exit 1
fi

for file in "$@"; do
    if [[ -e "$file" ]]; then
        base=$(basename -- "$file")
        dest="$TRASH_DIR/${base}.${TIMESTAMP}"
        mv -- "$file" "$dest"
        echo "Moved '$file' to trash as '$dest'"
    else
        echo "Warning: '$file' does not exist, skipping."
    fi
done

How This Works Internally

You can alias this so rm becomes safe by default:

echo "alias rm='saferm'" >> ~/.bashrc
source ~/.bashrc

Part 2: Building the Recovery Script for the Trash

Now let’s build the counterpart — a script to list and restore trashed files.

#!/usr/bin/env bash
set -euo pipefail

TRASH_DIR="$HOME/.local/share/bash-trash"

list_trash() {
    echo "Files currently in trash:"
    echo "-------------------------"
    ls -lt "$TRASH_DIR" 2>/dev/null || echo "Trash is empty."
}

restore_file() {
    local pattern="$1"
    local match
    match=$(find "$TRASH_DIR" -iname "*${pattern}*" | head -n 1)

    if [[ -z "$match" ]]; then
        echo "No trashed file matches '$pattern'."
        exit 1
    fi

    local original_name
    original_name=$(basename "$match" | sed -E 's/\.[0-9]{8}-[0-9]{6}$//')

    read -rp "Restore '$match' as './${original_name}'? [y/N] " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        mv -- "$match" "./$original_name"
        echo "Restored to ./$original_name"
    else
        echo "Cancelled."
    fi
}

case "${1:-}" in
    list)
        list_trash
        ;;
    restore)
        restore_file "${2:-}"
        ;;
    *)
        echo "Usage: $0 {list|restore <filename-pattern>}"
        exit 1
        ;;
esac

Explaining the Logic

Usage looks like this:

./recover.sh list
./recover.sh restore report

Part 3: Wrapping Deep Recovery Tools for Already-Deleted Files

If a file was deleted before you had this safety net in place, you’ll need to reach for lower-level tools. Here’s a Bash wrapper around testdisk/photorec that simplifies the process for ext4 filesystems:

#!/usr/bin/env bash
set -euo pipefail

DEVICE="${1:-}"
OUTPUT_DIR="${2:-./recovered}"

if [[ -z "$DEVICE" ]]; then
    echo "Usage: $0 /dev/sdXN [output-directory]"
    echo "Run 'lsblk' to identify your device/partition."
    exit 1
fi

mkdir -p "$OUTPUT_DIR"

if ! command -v photorec &>/dev/null; then
    echo "photorec is not installed. Install it with: sudo apt install testdisk"
    exit 1
fi

echo "WARNING: Deep recovery scans can take a long time and should"
echo "be run against an unmounted device or partition when possible."
read -rp "Continue scanning $DEVICE? [y/N] " confirm

if [[ "$confirm" =~ ^[Yy]$ ]]; then
    sudo photorec /d "$OUTPUT_DIR" /cmd "$DEVICE" search
else
    echo "Cancelled."
fi

What’s Happening Here

Real-World Use Cases

Automation Ideas

You can schedule a cron job to purge trash older than 30 days so it doesn’t grow forever:

0 3 * * * find "$HOME/.local/share/bash-trash" -type f -mtime +30 -delete

This runs at 3 AM daily and removes any trashed file older than 30 days, giving you a rolling recovery window without unlimited disk growth.

Security Considerations

Optimization Tips

Troubleshooting

Common Mistakes to Avoid

Frequently Asked Questions

Is it possible to recover a file after rm -rf with no trash safety net? Sometimes, using tools like extundelete or photorec, but success isn’t guaranteed and depends heavily on filesystem type and how much the disk has been used since deletion.

Does this work on ext4, NTFS, and APFS? The trash interceptor works on any filesystem since it’s just a mv operation. Deep recovery tool support varies — testdisk/photorec support most common filesystems including ext4, NTFS, and FAT32.

Should I use this instead of real backups? No. This is a safety net, not a backup strategy. Pair it with the Bash File Backup Tool described elsewhere in this series.

Summary

We built a two-layer file recovery system in Bash: a prevention layer that intercepts deletions and moves files to a recoverable trash location, and a recovery layer that wraps professional-grade tools like photorec for cases where files were already deleted before the safety net existed. The key lesson here is that true “undelete” magic doesn’t really exist in Bash — what we can do is minimize risk and make recovery as smooth as possible when things go wrong.

References

Exit mobile version