mr - Find most recent files matching a pattern

#!/usr/bin/env bash
help_text="
NAME
    mr - Find most recent files matching a pattern.

USAGE
    mr [options] <pattern>

OPTIONS
    -n|--number <number>
            Number of results to show.

    -i|--ignore-case
        Set a flag.

DESCRIPTION
    This script searches for files matching a shell pattern in the current
    directory and its subdirectories. It then sorts the results by
    modification time and displays the most recent ones.

AUTHOR
    mjnurse.github.io - 2026
"
help_line="Find most recent files matching a pattern"
web_desc_line="Find most recent files matching a pattern"

# set -x # for debugging

# Terminal Colours
cdef="\e[39m" # default colour
cbla="\e[30m"; cgra="\e[90m"; clgra="\e[37m"; cwhi="\e[97m"
cred="\e[31m"; cgre="\e[32m"; cyel="\e[33m"; cblu="\e[34m"; cmag="\e[35m"; ccya="\e[36m";
clred="\e[91m"; clgre="\e[92m"; clyel="\e[93m"; clblu="\e[94m"; clmag="\e[95m"; clcya="\e[96m"

function cecho {
    color=c$1; shift
    echo -e "${!color}$*${cdef}"
}

# Defaults
COUNT=20
CASE_INSENSITIVE=0

debug=false

usage() {
    echo "Usage: ${0##*/} [options] <pattern>"
    echo "Options:"
    grep "# arg:" $0 | grep -v "grep" | sed 's/^ *//; s/).*# *arg://'
    echo "Try ${0##*/} -h for more information"
    exit 1
}

# Split combined short arguments (eg -abc) into individual arguments (-a -b -c). Remove = symbols.
new_args=()
while [[ "$1" != "" ]]; do
    if [[ $1 == -* && $1 != --* && ${#1} -gt 2 ]]; then
        for ((i=1; i<${#1}; i++)); do
            arg="${1:i:1}"
            if [[ $arg == "=" ]]; then new_args+=("${1:i+1}"); break; fi
            if [[ $arg == "d" ]]; then debug=true; else new_args+=("-$arg"); fi
        done
    else
        if [[ $1 == --*=* ]]; then
            new_args+=("${1%%=*}"); new_args+=("${1##*=}")
        else
            if [[ $1 == "-d" || $1 == "--debug" ]]; then debug=true; else new_args+=("$1"); fi
        fi
    fi
    shift
done

if $debug; then
    echo "DEBUG ENABLED"
    printf "args: "; printf '%s ' "${new_args[@]}"; echo
    # set -x
fi

# Process the arguments
set -- "${new_args[@]}"
while [[ "$1" != "" ]]; do
    case "$1" in
        -n|--number) # arg: <number>     Number of results to show
            COUNT="$2"
            if [[ "$COUNT" == "" || "$COUNT" == -* ]]; then
                echo "Error: Missing value for $1"
                usage
            fi
            shift
            ;;
        -h|--help)
            echo -e "$help_text"
            exit 0
            ;;
        -i|--ignore-case) # arg:         Set a flag
            CASE_INSENSITIVE=1
            ;;
        -*|--*)
            echo "Unknown option: $1"
            usage
            ;;
        *)
            break
            ;;
    esac
    shift
done

# Require a pattern
if [[ $# -lt 1 ]]; then
  echo "Error: missing pattern." >&2
  usage
  exit 2
fi
PATTERN="$1"

# Choose find name flag based on case sensitivity
if [[ $CASE_INSENSITIVE -eq 1 ]]; then
  NAMEFLAG="-iname"
else
  NAMEFLAG="-name"
fi

tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT

# Note: Using while-read loop to handle arbitrary filenames safely.
while IFS= read -r -d '' f; do
  # If file was deleted between find and stat, skip errors
  if out=$(stat -c "%y" "$f" 2>/dev/null); then
    mtime=${out:2:14} # "YYYY-MM-DD HH:MM:SS"
    echo "${mtime// /-} $f"
  fi
done < <(find . -type d -name .git -prune -o -type f $NAMEFLAG "$PATTERN" -print0) > "$tmpfile"

# Show the N most recent
msg="$COUNT most recent files matching '$PATTERN':"
echo "$msg"
echo "${msg//?/-}"
sort -r -n -k1,1 "$tmpfile" | head -n "$COUNT" | cut -f2-