Homebrew: how to list the N last installed packages?

Viewed 1235

Simply put: I want to list the last N packages I've installed with Homebrew.

What is the best (and possibly fastest) way to accomplish this?

Note that I'm not fluent in Ruby, so any suggestions to 'hack the Homebrew code to do what you want' would get me nervous...

What I tried so far

  1. Read man pages, documentation, the Homebrew website, StackOverflow, googled with all sorts of variant questions, etc. No luck so far.
  2. brew info [formula|cask] will actually tell the date when a formula/cask has been poured (which I assume means 'installed' outside the Homebrewosphere). So that value must be written somewhere — a database? a log?
  3. Maybe there is an option to extract the poured date information via the JSON API? But the truth is that with Homebrew 3.1.9-121-g654c78c, I couldn't get any poured-date or similar element on the JSON output... the only dates that I get are related to git (presumably because they're more useful for Homebrew's internal workings). This would, in theory, be able to tell me what are the 'newest' versions of the formulae I have installed, but not the order I have installed them — in other words, I could have installed a year-old version yesterday, and I don't need to know that it's one year old, I only want to know I've installed it yesterday!

What I learned so far

Although I couldn't figure out how to retrieve that information, I'm sure it is there, since brew info ... will give the correct day a particular formula was poured. Thus, one possible solution would be to capture all the information from brew info and then do a grep on it; thus, something like brew info | grep Poured should give me what I want. Needless to say, this takes eternities to run (in fact, I never managed to complete it — I gave up after several minutes).

Of course, I found out that there is a brew info --installed option — but currently, it only works with JSON output. And since JSON output will not tell the poured date, this isn't useful.

A possibility would be to do it in the following way:

  • Extract all installed package names with brew info --installed --json=v1 | jq "map(.name)" > inst.json
  • Parse the result so that it becomes a single line, e.g. cat inst.json | tr -d '\n\r\[\]\"\,'
  • Now run brew info --formula (treat everything as a formula to avoid warnings) with that single line, pipe the result in another file (e.g. all-installed.txt)
  • Go through that file, extract the line with the formula name and the date, and format it using something like cat all-installed.txt | sed -E 's/([[:alnum:]]+):? stable.*\n(.*\n){3,7}^ Poured from bottle on (.*)$/\1 -- \3\\n/g' | sort | tail -40 — the idea is to have lines just with the date and the formula name, so that it can get easily sorted [note: I'm aware that the regex shown doesn't work, it was just part of a failed attempt before I gave up this approach]

Messy. It also takes a lot of time to process everything. You can put it all in a single line and avoid the intermediary files, if you're prepared to stare at a blank screen and wait for several minutes.

The quick and dirty approach

I was trying to look for a) installation logs; b) some sort of database where brew would store the information I was trying to extract (and that brew info has access to). Most of the 'logs' I found were actually related to patching individual packages (so that if something goes wrong, you can presumably email the maintainer). However, by sheer chance, I also noticed that every package has an INSTALL_RECEIPT.json inside /usr/local/Cellar/, which seems to have the output of brew info --json=v1 package-name. Whatever the purpose of this file, it has a precious bit of information: it has been created on the date that this package was installed!

That was quite a bit of luck for me, because now I could simply stat this file and get its creation timestamp. Because the formula directories are quite well-formed and easy to parse, I could do something very simple, just using stat and some formatting things which took me an eternity to figure out (mostly because stat under BSD-inspired Unixes has different options than those popular with the SysV-inspired Linux).

For example, to get the last 40 installed formulae:

stat -t '%Y%m%d%H%M' -f "%Sc %N %@" /usr/local/Cellar/*/*/INSTALL_RECEIPT.json | sort | tail -40

This runs reasonably fast, and I certainly managed to get exactly what I wanted/needed for another project (list the last few formulae that I had to install in order to run a certain software package that I'm attempting to get to compile under macOS and document what is needed), but I kept wondering if this is the 'best' approach. Sure, it's fast enough — it's way faster to traverse a filesystem tree using standard tools than expecting Ruby to do its slow-paced work — but how do I know that I have the 'correct' information? Do all packages have an INSTALL_RECEIPT.json? What if the Homebrew core developers think of completely changing the whole directory structure? Or stop writing that INSTALL_RECEIPT.json file because they use a database 'elsewhere' (where?) and this became redundant? In other words — how can I figure out what I want in a future-proof way, relying only on whatever information that the brew command already supplies?

Anyway, for the sake of the argument, I'd be quite welcome to see some suggestions on how to do this correctly — and without 'cheating' as I did with stat (e.g. not going through the brew command at all but rather relying on the filesystem itself).

Or maybe such functionality does not exist and I should put a feature request on Homebrew's GitHub issues? (After all, Debian/Ubuntu Linux users have a way to get that information using some parameters for apt and a bit of parsing... we 'deserve' the same, or even better! )

1 Answers

The "brew list" command has a -t option:

Sort formulae and/or casks by time modified, listing most recently modified first.

Thus to get the most recent 40, you could write:

brew list -1t 2> /dev/null | head -n 40

There's also a -l option, which shows the timestamps that are used.

Running brew list -t -v shows what's happening under the hood; on my machine, the output begins:

==> Formulae
ls -t /usr/local/Cellar

So it's clear that brew list is (currently) relying on the "Cellar".

If brew list is too slow for your liking, I believe it would be reasonable to use HOMEBREW_PREFIX (as revealed by brew --prefix) in combination with either Cellar or Cellar/*/*/INSTALL_RECEIPT.json

.time

The JSON object in each INSTALL_RECEIPT.json has a .time key, which would presumably be more reliable than a file or directory time stamp, so you might like to consider something like this bash script:

CELLAR=$(brew --prefix)/Cellar
jq -nr --arg cellar "$CELLAR" '
  [inputs | {time, file: (input_filename|sub($cellar;"") | sub("/INSTALL_RECEIPT.json";""))}]
  | sort_by(.time)[-40:][]
  | .file
' $CELLAR/*/*/INSTALL_RECEIPT.json
Related