Finding most recently updated projects with the 'recent' function
Tags: shell
Published: 2023-05-31 06:55:23

---
title: Finding most recently updated projects with the 'recent' function
author: ''
published: true
publishDate: Mon May 29 2023
displayDate: 29 May
cover: 'https://i.ibb.co/swkd4P2/ccc.jpg'
description: Finding most recently updated projects with the 'recent' function
tags:
  - shell
id: 1486665
date: '2023-05-31T06:55:23Z'
---

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

```bash
➜ 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
```

Lets look at the code

#### `recent`

```bash
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"
}
```

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 of `num` 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`

````bash
ColorCyan () {
  echo -ne '\e[36m'$1'\e[0m'
}
```

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`
```bash
astros () {
  [[ -n $1 ]] && recent $1 astro.config.mjs || recent 10 astro.config.mjs
}
```

## Usage

```bash
➜ 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
```
````