#!/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-