Every command I enter on my computer is written to a log file1. These logs have come in handy countless times; I tell anyone who will listen to save their logs too. My inspiration for doing this came from this atomic object post many years ago, and I am so glad I followed their advice. Can’t remember that random command you entered a year ago? That’s ok, it’s in your saved log directory; just go in there and grep
. Better yet, ripgrep.
Here’s an example. I know I ran some complicated decrypt-related workflow last spring, but I can’t remember what exactly I did:
There we go! I see a cluster of “decrypt” related commands the week of June 13 – 17 last year, I bet my answer is in one of those log files.
Configure zsh to save commands to log files
The article I linked in the intro above has instructions for how to set this up for a bash shell. If you’re using bash, feel free to jump over there and follow the instructions. If you’re on a Mac, however, you’re probably running zsh for your shell; it’s been the default for a few years now. Fear not, I have updated instructions.
Update your .zshrc (or your configuration file of choice)
You can modify your .zshrc
file itself, but I like to keep mine clean since that’s the default zsh file and the root of all shell config. For the sake of making this example easy though, let’s just pretend we’re adding this straight into .zshrc
. Here’s the full command (with explanation below):
# This is stolen almost exactly from the linked post
preexec() {if [ "$(id -u)" -ne 0 ]; then echo "$(date "+%Y-%m-%d.%H:%M:%S") $(pwd) $ $3" >> ~/.logs/zsh-history-$(date "+%Y-%m-%d").log; fi}
There’s a lot going on in that preexec
command, so let’s break it down.
First, what’s preexec?
I’ll let the zsh docs explain:
preexec
Executed just after a command has been read and is about to be executed.
Preexec2 is a built in zsh hook which allows you to do something just before your command is executed—the perfect time to write a log.
Next is this piece:
if ["$(id -u)" -ne 0]; then
That’s saying if you're not in sudo, then evaluate the rest of this
. This isn’t strictly necessary, but it feels better.
Next we have this:
echo "$(date "+%Y-%m-%d.%H:%M:%S") $(pwd) $ $3"
This line is doing a few things:
- Printing a datetime formatted like this: 2023-03-27.23:25:08
- Printing the pwd where the command was entered
- Printing a
$
for visual formatting (no effect, I just like it) $3
is the third argument given topreexec
, which is “the full text that is being executed”
As an example, this is what the most recent log line in my personal log history looks like:
2023-03-27.23:25:08 /Users/justin $ vim .zprompt
At 11:25PM, in my home directory, I entered vim .zprompt
while writing this post.
Final Piece
The last bit of the big command above is:
>> ~/.logs/zsh-history-$(date "+%Y-%m-%d").log; fi}
The >>
takes everything from its left and appends it to the file on the right, which is currently ~/.logs/zsh-history-2023-03-27.log
. That final semicolon and fi}
is just the end of the if
statement that started this whole thing.
The full command, one more time
preexec() {if [ "$(id -u)" -ne 0 ]; then echo "$(date "+%Y-%m-%d.%H:%M:%S") $(pwd) $ $3" >> ~/.logs/zsh-history-$(date "+%Y-%m-%d").log; fi}
The plain English recap of what this does:
- If the user is not sudo
- Prints a line with the format
yyyy-mm-dd.hh:mm:ss /current/directory $ {command}
- Appends that line to a log file at the path
~/.logs/zsh-history-yyyy-mm-dd.log
- Makes my life a lot easier
That’s it. I hope you take a minute and configure this also; you won’t regret it.
Why not use the builtin zsh history?
The builtin history is a good option! It requires some small setup to get going, but zsh does have builtin support for a history file. The main reason I like my custom setup is to have the log files split out by day. Sometimes when looking back through my history I have to re-contextualize the command I ran, which I find easier with smaller, day-specific log files. Granted, that wouldn’t be much more difficult via the builtin history, it’s more a matter of personal preference.
Helpful Links
- zsh documentation
- Atomic Object post from 2016
- Better Zsh History
- Helpful formatting commands for builtin zsh history
- A very detailed walkthrough of Bash history
Notes
- If you’re worried about this taking disk space, you don’t need to be. In three years on this computer my
.logs/
directory has only taken up 7M of space. ↩︎ - Why not use
precmd
? Precmd has no arguments, and would have to rely onhistory
, which means it can only write the command before the one you just entered. If you close a terminal window, or a command crashes it, you won’t have a record of the most recent command. ↩︎