Skip to content

Instantly share code, notes, and snippets.

@thaikolja
Last active April 20, 2025 22:20
Show Gist options
  • Select an option

  • Save thaikolja/89aeac652f5c53db72ca241efc0290b3 to your computer and use it in GitHub Desktop.

Select an option

Save thaikolja/89aeac652f5c53db72ca241efc0290b3 to your computer and use it in GitHub Desktop.
A flexible Bash script that can be used directly or via a cron job to automatically delete files and folders older than a specified number of days. A great script to keep log folders and other directories clean.

Old Files Cleaner

License: MIT Shell Script

Old Files Cleaner is a simple yet effective shell script that automatically deletes old files from a specified directory. It helps keep your file system tidy by removing files older than a certain number of days. You can also tell it to ignore specific folders and file types, and it even has a "dry-run"ng anything. This script is perfect for setting up as a cron job to clean up old mode so you can test it without actually deleti files regularly.

🚀 Features

Old Files Cleaner includes the following features:

  • 🗑️ Automated File Deletion: Deletes files older than a specified number of days.
  • 🎯 Target Directory: You can specify which directory to clean up.
  • 🚫 Exclude Directories: Option to prevent the script from deleting files in certain subdirectories.
  • 🗄️ Exclude File Patterns: Option to prevent the script from deleting files matching specific patterns (like file extensions).
  • 🧪 Dry-Run Mode: Test the script and see what would be deleted without actually deleting anything. This is useful for testing and making sure it works as expected!
  • ⏰ Cron Job Ready: Designed to be easily set up as a cron job for automatic, scheduled cleanups.

🛠️ Installation

This script is a simple bash script, so there's no real "installation" needed. Just follow these steps:

  1. Download the script: Save the script content to a file named auto-deleter.sh. You can use wget or curl if you are downloading it from somewhere, or simply copy and paste the code into a new file using a text editor.
  2. Make it executable: Open your terminal and navigate to the directory where you saved auto-deleter.sh. Then, make the script executable by running:
chmod +x auto-deleter.sh

That's it! The script is now "installed" and ready to use.

⚙️ Configuration & Usage

You run the script directly from your terminal. Here's how to use it and the options you can configure:

Basic Usage:

./auto-deleter.sh

This will run the script with default settings (if any are hardcoded in the script - check the script itself for defaults). To be effective, you'll likely want to use the options below.

Options:

  • --dry-run: Run the script in dry-run mode. This will show you what files would be deleted without actually deleting them. Very useful for testing!

    ./auto-deleter.sh --dry-run
  • --target-dir <directory>: Specify the directory you want to clean up. Replace <directory> with the actual path to the directory. If you don't specify this, the script might have a default directory set internally (check the script).

    ./auto-deleter.sh --target-dir /path/to/your/target/directory
  • --exclude-dir <directory>: Specify a directory to exclude from deletion. You can use this option multiple times to exclude more than one directory. Replace <directory> with the directory name you want to exclude (relative to the target directory or absolute path).

    ./auto-deleter.sh --target-dir /path/to/target --exclude-dir important_folder
    ./auto-deleter.sh --target-dir /path/to/target --exclude-dir folder1 --exclude-dir folder2
  • --exclude-file <pattern>: Specify a file pattern to exclude from deletion. This uses standard file pattern matching (like *.txt for all text files). You can use this option multiple times to exclude multiple patterns.

    ./auto-deleter.sh --target-dir /path/to/target --exclude-file "*.log"
    ./auto-deleter.sh --target-dir /path/to/target --exclude-file "*.txt" --exclude-file "*.csv"

Example Usage with Options:

To do a dry-run cleanup of the /home/user/downloads directory, excluding any folder named archive and any files ending in .temp, you would run:

./auto-deleter.sh --dry-run --target-dir /home/user/downloads --exclude-dir archive --exclude-file "*.temp"

Setting up as a Cron Job (Automatic Scheduling):

This script is designed to be run automatically on a schedule using cron. Here's how to set it up to run daily at midnight:

  1. Open your crontab: In your terminal, type:

    crontab -e

    This will open your crontab file in a text editor. If it's your first time, you might be asked to choose an editor.

  2. Add the cron job: Add the following line to the crontab file. Make sure to replace /path/to/auto-deleter.sh with the actual path to where you saved your auto-deleter.sh script.

    0 0 * * * /path/to/auto-deleter.sh --target-dir /path/to/your/target/directory --exclude-dir important_folder
    
    • 0 0 * * *: This part schedules the script to run at midnight every day.
    • /path/to/auto-deleter.sh: Replace this with the full path to your auto-deleter.sh script. For example, if you saved it in your home directory, it might be /home/yourusername/auto-deleter.sh.
    • --target-dir /path/to/your/target/directory --exclude-dir important_folder: Add any options you want to use every time the script runs automatically. Remember to customize these options!
  3. Save and exit: Save the crontab file and exit the editor. Cron will automatically pick up the changes.

Important Notes for Cron Jobs:

  • Full Paths: When using cron, using full paths to your script and any directories is best. Cron environments are often minimal and might not have the same environment variables set as your interactive shell.
  • Logging: For cron jobs, especially those that delete files, it's a good idea to add logging to your script to record what it does. You can redirect output to a log file within the script using redirection like >> /path/to/logfile.log 2>&1.

🧪 Testing

Before setting up a cron job or running the script on important data, always use the --dry-run option first! This will let you see exactly which files the script intends to delete without actually deleting anything. Check the output carefully to make sure it's behaving as you expect.

👨‍💻 Author

  1. Kolja Nolte [email protected]

📜 License

This project is licensed under the MIT License. See the LICENSE file for details. (You can add a LICENSE file to your repository with the MIT License text if you wish).

#!/bin/bash
# Define the script to execute with bash
# Script Name: Old Files Cleaner
# Description: Deletes files older than a specified age (days) within a target
# directory, excluding specified directories and file patterns.
# Includes dry-run mode. Meant to be used as a cron job.
# Author: Kolja Nolte
# Email: [email protected]
# Website: https://www.kolja-nolte.com
# Date: 2025-04-21
# Version: 1.0.0
# License: MIT License
#
# Dependencies: bash, find, xargs, rm, date, printf
#
# Usage: ./auto-deleter.sh [--dry-run] [--target-dir <dir>] [--exclude-dir <dir>] [--exclude-file <pattern>]
# Example cron job: 0 0 * * * /path/to/auto-deleter.sh (daily at midnight)
# --- Default Configuration ---
# Define if the script should run in dry-run mode
DRY_RUN=false
# Define the target directory to clean
TARGET_DIR="/mnt/storage/backup/uploads"
# Define the age (in days) of files to be deleted
DAYS_OLD=90
# Define an array of directories to exclude from the cleanup process
EXCLUDE_DIRS=(".well-known")
# Define an array of file patterns to exclude from the cleanup process
EXCLUDE_FILES=("index.php" "index.html")
# --- Function Definitions ---
# Define the log function
log() {
# Output the current date and time, followed by the provided message
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# Define the log_error function
log_error() {
# Output the current date and time, followed by "ERROR:" and the provided message to standard error
echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: $1" >&2
}
# Define the usage function
usage() {
# Display the script's usage instructions
echo "Usage: $0 [-n] [-t <dir>] [-d <days>] [-e <dir>] [-f <pattern>] [-h]"
# Display the available options
echo "Options:"
# Explain the -n option
echo " -n Dry-run mode. List files that would be deleted without deleting them."
# Explain the -t option
echo " -t <dir> Target directory to clean. (Default: ${TARGET_DIR})"
# Explain the -d option
echo " -d <days> Delete files older than this many days. Must be a non-negative integer. (Default: ${DAYS_OLD})"
# Explain the -e option
echo " -e <dir> Directory name (relative to target) to exclude. Can be used multiple times."
# Explain the default exclusions for directories
echo " (Default exclusions: ${EXCLUDE_DIRS[*]})"
# Explain the -f option
echo " -f <pattern> File name pattern (e.g., '*.log', 'index.php') to exclude. Can be used multiple times."
# Explain the default exclusions for files
echo " (Default exclusions: ${EXCLUDE_FILES[*]})"
# Explain the -h option
echo " -h Show this help message and exit."
}
# Define the err_report function
err_report() {
# Store the line number where the error occurred
local line_num="$1"
# Store the exit code (defaulting to 1 if not provided)
local exit_code="${2:-1}"
# Log an error message indicating the script failure and the location
log_error "Script failed on or near line ${line_num} with exit code ${exit_code}."
# Exit the script with the provided exit code
exit "$exit_code"
}
# --- Argument Parsing ---
# Initialize a flag to track if CLI excluded directories are set
cli_exclude_dirs_set=false
# Initialize a flag to track if CLI excluded files are set
cli_exclude_files_set=false
# Parse command-line options using getopts
while getopts ":hnt:d:e:f:" opt; do
# Use a case statement to handle each option
case $opt in
# If the -h option is provided
h)
# Display usage information and exit
usage; exit 0 ;;
# If the -n option is provided
n)
# Set DRY_RUN to true
DRY_RUN=true ;;
# If the -t option is provided
t)
# Set TARGET_DIR to the provided argument
TARGET_DIR="$OPTARG" ;;
# If the -d option is provided
d)
# Validate DAYS_OLD is a non-negative integer
if [[ "$OPTARG" =~ ^[0-9]+$ ]]; then
# Set DAYS_OLD to the provided argument if it's valid
DAYS_OLD="$OPTARG"
else
# Log an error message if the value is invalid
log_error "Invalid value for days (-d): '$OPTARG'. Must be a non-negative integer."
# Display usage information
usage
# Exit with an error code
exit 1
fi
;;
# If the -e option is provided
e)
# Check if this is the first -e option
if ! $cli_exclude_dirs_set; then
# Clear the default EXCLUDE_DIRS array
EXCLUDE_DIRS=()
# Set the cli_exclude_dirs_set flag to true
cli_exclude_dirs_set=true
fi
# Add the provided directory to the EXCLUDE_DIRS array
EXCLUDE_DIRS+=("$OPTARG")
;;
# If the -f option is provided
f)
# Check if this is the first -f option
if ! $cli_exclude_files_set; then
# Clear the default EXCLUDE_FILES array
EXCLUDE_FILES=()
# Set the cli_exclude_files_set flag to true
cli_exclude_files_set=true
fi
# Add the provided file pattern to the EXCLUDE_FILES array
EXCLUDE_FILES+=("$OPTARG")
;;
# If an invalid option is provided
\?)
# Log an error message indicating the invalid option
log_error "Invalid option: -$OPTARG"
# Display usage information
usage
# Exit with an error code
exit 1 ;;
# If an option requiring an argument is missing the argument
:)
# Log an error message indicating the missing argument
log_error "Option -$OPTARG requires an argument."
# Display usage information
usage
# Exit with an error code
exit 1 ;;
esac
done
# Shift the option index to remove parsed options
shift $((OPTIND-1))
# --- Script Execution ---
# Enable strict mode: exit on error (-e), undefined variable (-u), pipe failure (-o pipefail)
set -euo pipefail
# Trap errors to call err_report function
trap 'err_report "$LINENO" "$?"' ERR
# Validate target directory
if [ ! -d "$TARGET_DIR" ]; then
# Log an error if the target directory does not exist or is not a directory
log_error "Target directory '$TARGET_DIR' does not exist or is not a directory."
# Exit with an error code
exit 1
fi
# Log configuration
log "Starting cleanup process..."
log "Target Directory: '$TARGET_DIR'"
log "Delete files older than: $DAYS_OLD days"
log "Excluded Directories: ${EXCLUDE_DIRS[*]:-(none)}"
log "Excluded File Patterns: ${EXCLUDE_FILES[*]:-(none)}"
log "Mode: $(if $DRY_RUN; then echo 'Dry Run'; else echo 'Actual Deletion'; fi)"
# --- Build find command ---
# Initialize the find command with the target directory and minimum depth
find_cmd=(find "$TARGET_DIR" -mindepth 1)
# Add directory exclusions (pruning)
if [ ${#EXCLUDE_DIRS[@]} -gt 0 ]; then
# Start pruning group
find_cmd+=(\()
# Initialize a flag to track the first directory
first_dir=true
# Iterate over the EXCLUDE_DIRS array
for dir_pattern in "${EXCLUDE_DIRS[@]}"; do
# Check if the directory pattern is empty
if [[ -z "$dir_pattern" ]]; then
# Log a warning if the directory pattern is empty
log "Warning: Skipping empty directory exclusion pattern."
# Skip to the next iteration
continue
fi
# Add an OR condition if it's not the first directory
if ! $first_dir; then
# Add OR condition
find_cmd+=(-o)
fi
# Clean the pattern: remove leading/trailing slashes for reliable matching with find's output
clean_pattern=$(echo "$dir_pattern" | sed 's:/*$::; s:^/*::')
# Use -path which matches the whole path string. Need to match relative to TARGET_DIR.
find_cmd+=(-path "$TARGET_DIR/$clean_pattern")
# Set the first_dir flag to false
first_dir=false
done
# End group, prune matching paths, OR continue with main criteria
find_cmd+=(\) -prune -o)
fi
# Add main selection criteria (type, age, file exclusions)
find_cmd+=(\()
# Match regular files older than N days
find_cmd+=(-type f -mtime "+$DAYS_OLD")
# Add file pattern exclusions
if [ ${#EXCLUDE_FILES[@]} -gt 0 ]; then
# Start NOT group for file patterns
find_cmd+=(-not \()
# Initialize a flag to track the first file
first_file=true
# Iterate over the EXCLUDE_FILES array
for file_pattern in "${EXCLUDE_FILES[@]}"; do
# Check if the file pattern is empty
if [[ -z "$file_pattern" ]]; then
# Log a warning if the file pattern is empty
log "Warning: Skipping empty file exclusion pattern."
# Skip to the next iteration
continue
fi
# Add an OR condition if it's not the first file
if ! $first_file; then
# Add OR condition
find_cmd+=(-o)
fi
# Match the file name
find_cmd+=(-name "$file_pattern")
# Set the first_file flag to false
first_file=false
done
# End NOT group
find_cmd+=(\))
fi
# End main criteria group, print null-terminated results
find_cmd+=(\) -print0)
# --- Execute find and process results ---
# Log "Searching for files to delete..."
log "Searching for files to delete..."
# Execute the find command, capturing null-separated output
file_list_null=$("${find_cmd[@]}")
# Count the number of files found
file_count=$(echo -n "$file_list_null" | tr '\0' '\n' | grep -c .) || true
# Log the number of files found
log "Found $file_count file(s) matching the criteria."
# --- Perform Action (Dry Run or Deletion) ---
# Check if dry run mode is enabled
if [ "$DRY_RUN" = true ]; then
# Log "--- DRY RUN MODE ---"
log "--- DRY RUN MODE ---"
# Check if any files were found
if [ "$file_count" -gt 0 ]; then
# Log "Files that would be deleted:"
log "Files that would be deleted:"
# Print the list, converting nulls to newlines for readability
echo -n "$file_list_null" | tr '\0' '\n'
else
# Log "No files matched the criteria. Nothing would be deleted."
log "No files matched the criteria. Nothing would be deleted."
fi
# Log "--- END DRY RUN ---"
log "--- END DRY RUN ---"
else
# Log "--- ACTUAL DELETION MODE ---"
log "--- ACTUAL DELETION MODE ---"
# Check if any files were found
if [ "$file_count" -gt 0 ]; then
# Log "Proceeding with deletion of $file_count file(s)..."
log "Proceeding with deletion of $file_count file(s)..."
# Pipe the null-separated list directly to xargs for deletion
echo -n "$file_list_null" | xargs -0 --no-run-if-empty rm -f
# Log "Deletion command executed for $file_count file(s)."
log "Deletion command executed for $file_count file(s)."
else
# Log "No files matched the criteria. Nothing to delete."
log "No files matched the criteria. Nothing to delete."
fi
fi
# Log "Cleanup process completed successfully."
log "Cleanup process completed successfully."
# Exit with success code
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment