mr - Example Bash Script

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

USAGE
    mr [options] <pattern>

OPTIONS
    -n N
        Number of results to show (default: 20).

    -i
        Case-insensitive matching.

    -h|--help
        Show this help text.

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="Example Bash Script"
web_desc_line="Example Bash Script"

# 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}"
}

try="Try ${0##*/} -h for more information"
tmp="${help_text##*USAGE}"
usage=$(echo "Usage: ${tmp%%OPTIONS*}" | tr -d "\n" | sed "s/  */ /g")

# if [[ "$1" == "" ]]; then
#     echo "${usage}"
#     echo "${try}"
#     exit 1
# fi

while [[ "$1" != "" ]]; do
    case $1 in
        -h|--help)
            echo "$help_text"
            exit
            ;;
        ?*)
            break
            ;;
    esac
    shift
done

set -euo pipefail

usage() {
  cat <<'EOF'
Usage: recent-matching.sh [-n N] [-i] "pattern"
  -n N   number of results to show (default: 20)
  -i     case-insensitive matching (uses find -iname)
  pattern is a shell pattern (as used by find -name/-iname), e.g. "martin*.py"
Search starts at the current directory and recurses into subdirectories.
EOF
}

# Defaults
COUNT=20
CASE_INSENSITIVE=0

# Parse options
while getopts ":n:ih" opt; do
  case "$opt" in
    n) COUNT="$OPTARG" ;;
    i) CASE_INSENSITIVE=1 ;;
    h) usage; exit 0 ;;
    \?) echo "Unknown option: -$OPTARG" >&2; usage; exit 2 ;;
    :)  echo "Option -$OPTARG requires an argument." >&2; usage; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

# 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 "$PATTERN")
else
  NAMEFLAG=(-name "$PATTERN")
fi

# We will:
#  1) find matching regular files, null-delimited (safe for odd filenames)
#  2) for each file, print "epoch_mtime<TAB>path"
#  3) sort numerically by mtime desc
#  4) take top N
#  5) print paths
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT

# Collect "mtime<tab>path" lines
# 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
    # 'out' is "mtime path"; convert first space to tab for robust sort/cut
    # (path itself may contain spaces)
    mtime=${out:2:14} # "YYYY-MM-DD HH:MM:SS"
    echo "${mtime// /-} $f"
  fi
done < <(find . -type f "${NAMEFLAG[@]}" -print0) > "$tmpfile"

# Show the N most recent
# If you want the timestamp alongside, remove the 'cut' and keep the full line.
# cat "$tmpfile"
sort -r -n -k1,1 "$tmpfile" | head -n "$COUNT" | cut -f2-