Professor Henry Jones: Well, he who finds the Grail must face the final challenge.
Indiana Jones: What final challenge?
Professor Henry Jones: Three devices of such lethal cunning.
Indiana Jones: Booby traps?
Professor Henry Jones: Oh, yes. But I found the clues that will safely take us through them in the Chronicles of St. Anselm.
Indiana Jones: [pleased] Well, what are they?
[short pause as Henry tries to recall]
Indiana Jones: Can’t you remember?
Professor Henry Jones: I wrote them down in my diary so that I wouldn’t have to remember.
***
Recipe for living a good life in the shell:
Make sure it’s fast.
Make sure its history can grow nearly infinitely and you can fuzzy-search through it.
The first one–fast shells–we talked about last week, so this time, let’s talk about shell history.
On one of my machines the ~/.zsh_history
file contains 26278 commands. Its oldest entry was recorded on April 25, 2019. When I hit Ctrl-r
in ZSH fzf pops up and lets me fuzzy-search through all 26278 commands, through the last 5 years of my shell history. It’s glorious.
That one curl
command with these four strange HTTP headers that I had to send to make sure I could talk to my local dev env correctly through the reverse-proxy – that’s in there, a few keystrokes and some hazy memories away. That one rsync
that uses the correct SSH client binary on the other side and that preserves timestamps and permission – also in there. The ugly bastard of a command that combined git grep
with find
and 8 ugly incantations of AWK chained together – locked in there too. The test command I ran yesterday is as reachable as the one command with 12 &&
in it that I ran 3 years ago, all thanks to a long shell history file.
Truly: ~/.zsh_history
is one of the most important files on my machine. I make use of it tens of times every day, whenever I hit Ctrl-r
. If I wasn’t such a doofus it would contain even more than 26278 commands, because I’ve been keeping a long shell history for nearly a decade. But since I am such a doofus, I forgot to back it up (or deleted the backup too early) a few times and hence the ~/.zsh_history
on the machine I’m typing this only contains 1034 commands.
Someone might say: “well, if the command was that important, why don’t you save it to a script?” And to that I say: look, man, if I knew in advance which command would turn out to be important, then we wouldn’t be here, because I’d already have ascended to the astral realm.
Also: why would I record it in a script if I can just configure my shell to keep track of all of those commands for me? It’s cheap, it’s fast, there’s essentially no downsides to it: the largest ~/.zsh_history
I have is 2MB and I can fuzzy-search through it in milliseconds.
***
So, to repeat: your shell – ZSH, Bash, Fish, or others – can record every command you execute in it and I’m begging you to make sure it is configured to do that.
Make sure it keeps track of a lot of commands. So many commands that when you put the number in your rc-file you think: that’s ridiculous, why would I need to record that many commands? Double that number. As the next and final step you need to get a fuzzy-finder fuzzy-finder to search through it.
Here’s the ZSH configuration I used for many, many years:
##########
# HISTORY
##########
HISTFILE=$HOME/.zsh_history
HISTSIZE=50000
SAVEHIST=50000
# Immediately append to history file:
setopt INC_APPEND_HISTORY
# Record timestamp in history:
setopt EXTENDED_HISTORY
# Expire duplicate entries first when trimming history:
setopt HIST_EXPIRE_DUPS_FIRST
# Dont record an entry that was just recorded again:
setopt HIST_IGNORE_DUPS
# Delete old recorded entry if new entry is a duplicate:
setopt HIST_IGNORE_ALL_DUPS
# Do not display a line previously found:
setopt HIST_FIND_NO_DUPS
# Dont record an entry starting with a space:
setopt HIST_IGNORE_SPACE
# Dont write duplicate entries in the history file:
setopt HIST_SAVE_NO_DUPS
# Share history between all sessions:
setopt SHARE_HISTORY
# Execute commands using history (e.g.: using !$) immediatel:
unsetopt HIST_VERIFY
That was combined with fzf to fuzzy-search through the history on ctrl-r
.
Configure your shell roughly like that and there you are: living the good shell life.
Or you can use Atuin, which I’ve been trying out for the last few weeks and have come to love. It’s a drop-in enhancement of your shell’s history functionality and not only gives you everything I described above – incredibly long shell history and fuzzy-search through it – but also syncing and backup (yup, need that) of shell history across multiple machines, filtering commands by machine, working-directory-specific history and probably 13 other things I forgot. It’s very nice. Highly recommend it.
Whatever you use: make sure your shell history is recorded, make sure your shell history can grow ridiculously long, and get a fuzzy-search for it.
The oldest entry in my history.sql is dated Wed Oct 17 13:07:38 CEST 2012. It's a bit of a hack, using PROMPT_COMMAND to push entries into this table:
CREATE TABLE history (pid INTEGER, host TEXT, date INTEGER, cwd TEXT, starttime INTEGER, command TEXT);
It's pretty much a gross hack that started with "history 1" and grew to include this and that, needed some formatting, needed a tmpfile (though probably not, really) and then squeeze it into the db.
export PROMPT_COMMAND='printf "%s^A%s^A%s^A%s^A%s\n" "$$" "$(hostname)" "$(date +%s)" "$(pwd)" "$(HISTTIMEFORMAT="%s^A" history 1 | sed "s/^ *[^ ]* *//g")" > /tmp/hinser;sqlite3 -separator ^A ~/someplace/bash_history.sql ".import /tmp/hinser history"'
The ^As are literal 001 to act as separators, I tried a number of other ways of separating the fields, ended up with ^A since that character seldom shows up in cmdlines or in any of the other fields.
Currently there are 198K lines of history, dumped to text it amounts to about 20MBs of text.
Love atuin ! One of the better shell feature replacements i have come to appreciate this past year