Overview
Several years ago I wrote a Bash script to perform a task I need to
perform almost every day - find the newest file in a series of
files.
At this point I was running a camera on a Raspberry Pi which was
attached to a window and viewed my back garden. I was taking a picture
every 15 minutes, giving them names containing the date and time, and
storing them in a directory. It was useful to be able to display the
latest picture.
Since then, I have found that searching for newest files useful in
many contexts:
Find the image generated by my random recipe chooser, put in the
clipboard and send it to the Telegram channel for my family.
Generate a weather report from
wttr.in
and send it
to Matrix.
Find the screenshot I just made and put it in the
clipboard.
Of course, I could just use the same name when writing these various
files, rather than accumulating several, but I often want to look back
through such collections. If I am concerned about such files
accumulating in an unwanted way I write
cron
scripts which
run every day and delete the oldest ones.
Original script
The first iteration of the script was actually written as a Bash
function which was loaded at login time. The function is called
newest_matching_file
and it takes two arguments:
A file glob expression to match the file I am looking
for.
An optional directory to look for the file. If this is omitted,
then the current directory will be used.
The first version of this function was a bit awkward since it used a
for
loop to scan the directory, using the glob pattern to
find the file. Since Bash glob pattern searches will return the search
pattern when they fail, it was necessary to use the
nullglob
(see references) option to prevent this, turning
it on before the search and off afterwards.
This technique was replaced later with a pipeline using the
find
command.
Improved Bash script
The version using
find
is what I will explain here.
function newest_matching_file {
local glob_pattern=${1-}
local dir=${2:-$PWD}
# Argument number check
if [[ $# -eq 0 || $# -gt 2 ]]; then
echo 'Usage: newest_matching_file GLOB_PATTERN [DIR]' >&2
return 1
fi
# Check the target directory
if [[ ! -d $dir ]]; then
echo "Unable to find directory $dir" >&2
return 1
fi
local newest_file
# shellcheck disable=SC2016
newest_file=$(find "$dir" -maxdepth 1 -name "$glob_pattern" \
-type f -printf "%T@ %p\n" | sort | sed -ne '${s/.\+ //;p}')
# Use printf instead of echo in case the file name begins with '-'
[[ -n $newest_file ]] && printf '%s\n' "$newest_file"
return 0
}
The function is in the file
newest_matching_file_1.sh
,
and it's loaded ("sourced", or declared) like this:
. newest_matching_file_1.sh
The
'.'
is a short-hand version of the command
source
.
I actually have two versions of this function, with the second one
using a regular expression, which the
find
command is able
to search with, but I prefer this one.
Explanation
The first two lines beginning with
local
define
variables local to the function holding the arguments. The first,
glob_pattern
is expected to contain some