DEV Community

cerico
cerico

Posted on • Updated on

Finding most recently updated projects with the 'recent' function

Finding most recently updated projects with the 'recent' function

The purpose of this recent function is to find and print the most recently updated directories containing a specific file or folder, relative to the current directory. Lets say for example we wanted to find the 6 most recently updated directories containing a Makefile, showing the date of the last updated file in each directory (ie not the Makefile itself)

Usage

➜ recent 6 Makefile
Finding 6 most recent directories containing Makefile
---
2023-05-28 venlo
2023-05-08 observatory
2023-05-08 lighthouse-ii
2023-05-08 docker/getting-started/vaxjo
2023-05-08 contabo
2023-05-07 research/seacroft

19 total
Enter fullscreen mode Exit fullscreen mode

Lets look at the code

recent

recent () { # Find n most recent directories containing named file # ➜ recent 12 astro.config.mjs
  [[ $1 = [1-9]* ]] && num=$1 || num=10
  [[ $1 = [.[:alpha:]]* ]] && f=$1 || f='.git'
  [[ $2 = [1-9]* ]] && num=$2
  [[ $2 = [.[:alpha:]]* ]] && f=$2
  local tmpfile=$(mktemp)
  echo Finding $(ColorCyan $num) most recent directories containing $(ColorGreen $f)
  echo ---
  find . -maxdepth 5  -not -path '*node_modules*' -name $f -print 2>/dev/null | while read -r line; do
    local mod_date=$(_most_recent_in $line)
    local formatted_dir=$(_format_dir_path $line)
    echo "$mod_date $formatted_dir" >> "$tmpfile"
  done
  sort -r "$tmpfile" | head -n $num
  echo ""
  echo "$(ColorCyan $(wc -l < "$tmpfile")) total"
  rm "$tmpfile"
}
Enter fullscreen mode Exit fullscreen mode

Lets break down whats happening here.

  • [[ $1 = [1-9]* ]] && num=$1 || num=10

If the first argument is a number, then set the variable num to this value. Otherwise, sets num to the default value of 10.

  • [[ $1 = [.[:alpha:]]* ]] && f=$1 || f='.git'

But if the first argument is a string then set the variable f to this value. Otherwise, sets f to the default value of '.git'.

  • [[ $2 = [1-9]* ]] && num=$2

If the second argument is a number, then set the variable num to this value. Otherwise, num remains its previous value, set on the first line.

  • [[ $2 = [.[:alpha:]]* ]] && f=$2

And if the second argument is a string then set the variable f to this value. Otherwise, f remains its previous value, set on the second line.

  • local tmpfile=$(mktemp)

Here a tmp file tmpFile is creatd, to store the results of the search.

  • find . -maxdepth 5 -not -path '_node_modules_' -name $f -print 2>/dev/null | while read -r line; do

Here we execute a find command, searching for the file or directory specified by the variable f. We're limiting the search to a maximum depth of 5 directories, and excluding any directories named node_modules. We're then piping the results of the find command to a while loop.

  • local mod_date=$(_most_recent_in $dir)

Inside the while loop, we're calling the function _most_recent_in, passing in the line from the loop. The function _most_recent_in returns the date of the most recently updated file or directory. We'll cover how this works in the next section.

  • local formatted_dir=$(_format_dir_path $dir)

Similarly, also inside the while loop, we're using the _format_dir_path function to format the line from the loop. We'll also cover this in the next section.

  • echo "$mod_date $formatted_dir" >> "$tmpfile"

Finally, inside the while loop, we're printing the date and directory path of each found f to the tmp file tmpFile

  • eort -r "$tmpfile" | head -n $num

And now back outside the loop, the temp file contains a list of all the found f files and/or directories, which are then sorted in reverse order, and limited to a count ofnum` specified at the start of the function

_most_recent_in

bash
_most_recent_in () {
[[ ! -n $1 ]] && return
[[ -f $1 ]] && term=$(dirname "$1") || term=$1/..
if [ $(uname) = 'Darwin' ]
then
find $term -type f -exec stat -f "%Sm" -t "%Y-%m-%d" {} + | sort -r | head -n 1
else
find $term -type f -exec stat --format="%y" {} + | sort -r | head -n 1 | cut -d' ' -f1
fi
}

Here we take in the line from the recent function outlined in the preceding section, and return the date of the most recently updated file, relative to the passed in line. Lets look in more detail

  • [[ -f $1 ]] && term=$(dirname "$1") || term=$1/..

If the passed in line is a file, we set the variable term to the directory of the file. Otherwise, we set term to the parent directory of the passed in line. So if the passed in line is reponame/README.md it will search for the most recently updated file in reponame, and if the passed inline is reponame/styles, where styles is a directory, it will search in reponame/styles/.., which is reponame. This ensure consistency, ie in both cases it searches the directory conraing what could be either a file or a subdirectory, not within the subdirectory itself.

The rest of the function finds the stat of each file in the directory, sorts them in reverse order, and returns the first result. We're returning just the date here as we don't need the file or directory name. THe find syntax being slightly different for Mac and Linux, we check the uname first before running the approprate find command

_format_dir_path

bash
_format_dir_path () {
echo $1 | awk '{sub(/\/[^\/]*$/, ""); print}' | awk -F'\\./' '{if ($2 == "") print "."; else print $2}'
}

This admittedly dense oneliner, strips the ./ from the beginning of the passed in path, and removes the final slash and everyhing after it. In the case of the file being in the current directory, eg ./Makefile, there is nothing to display, so it substitutes a .. This last function only exists for tidier return values, ie reponame and not ./reponame/styles

ColorCyan

`

ColorCyan () {
  echo -ne '\e[36m'$1'\e[0m'
}
Enter fullscreen mode Exit fullscreen mode

A small helper function used by recent to color code output

Extending the function

We can now extend the function to find commonly searched for directories. Lets say we commonly search for the most recently updated astro applications. We can make an astros function that calls the recent

astros

astros () {
  [[ -n $1 ]] && recent $1 astro.config.mjs || recent 10 astro.config.mjs
}
Enter fullscreen mode Exit fullscreen mode

Usage

➜ astros 4
Finding 4 most recent directories containing astro.config.mjs
---
2023-05-28 venlo/template/astro
2023-05-28 dev.io37.ch
2023-05-07 seacroft
2023-04-15 created-by-venlo/bus-station

22 total
Enter fullscreen mode Exit fullscreen mode

`
Enter fullscreen mode Exit fullscreen mode

Top comments (0)