I spend an awful lot of my day in Unix terminals running shell commands. For
some reason, the variance in efficiency between different people when using the shell
is huge: I know people who can run rings around me, and I’ve come across
more than one paid professional who doesn’t use the “up” key to retrieve the
previous command.
I chose that last example very deliberately: most of the commands most of us
run in the shell are highly repetitive. I typically run around 50-100 unique
(i.e. syntactically distinct) shell commands per working day — but
I’ll often run a tiny subset of those commands (e.g. cargo test
) hundreds of
time in a single day.
Since many command-line tools have hard-to-remember options, we can save huge
chunks of time – not to mention make fewer errors – if we can search our
shell history to find a previous incantation of a command we want to run. In
this post I’m going to show how, with little effort, searching shell
history can look like this:
Searching shell history
Larger Unix shells such as Bash have long allowed users to search through their
shell history by pressing Ctrl-r
and entering a substring. If I (in order)
executed the commands cat /etc/motd
then cat /etc/rc.conf
, then Ctrl-r
followed by “cat” will first match cat /etc/rc.conf
; pressing Ctrl-r
again
will cycle backwards for the next match which is cat /etc/motd
. I almost
never used this feature, because substring matching is too crude.
For example, I may know the command I want is cat
, the leaf
name I’m looking for is motd
but I don’t remember the directory: substring
matching can’t help me find what I’m looking for. Instead, I regularly used
grep
(with wildcards) to search through my shell’s history file instead.
For me, the game changer was pairing Ctrl-r
with
fzf, which brought two changes. First,
matching is “fuzzy”, so I can type “c mo” and cat /etc/motd
will be
matched. Second, multiple matches are shown at once. Typing “cat” will show
me several cat
commands, allowing me to quickly select the right
incantation (which may not have been the most recent).
It’s difficult for me to overstate how powerful a feature this is. Few things
in life make me as happy as pressing ctrl-R
then typing “l1
” and having a 100
character command-line execution that runs a complicated debugging tool, with
multiple environment variables set, whose output gets put in /tmp/l1
appear
in my terminal.
Using Ctrl-r
and fzf roughly doubled my efficiency in the shell
overnight. Interestingly, it had an even greater long term effect: I became a
more ambitious user of shell commands because I knew I could outsource my
memory to fzf. For example, since it’s now very easy to recall past commands, I
no longer set global environment variables, which had previously caused me
grief when I forgot about them . Now I set environment variables on a
per-command basis, knowing that I can recall them with Ctrl-r
and fzf.
For many years my favoured shell was zsh. When I later moved from zsh to fish,
Ctrl-r
and fzf was the first thing I configured; and when I moved back to zsh
, and redid my configuration from scratch, Ctrl-r
and fzf was again the first
thing I got working (shortly followed by
autosuggestions). If you
take nothing else from this
post than “Ctrl-r
and fzf are a significant productivity boon for Unix
users”, then I will have done something useful.
No tool, of course, is perfect. A
couple of months back I somehow stumbled across
skim, an fzf-alike that out-of-the-box
happens to suit me just a little bit better than fzf. The differences
are mostly minor, and you won’t go far wrong with either tool. That said,
I find that skim’s matching more often finds the commands I want quickly,
I prefer skim’s UI, and I find it easier to install skim on
random boxes — small advantages, perhaps, but enough for switching to be
worth it for me.
Doing even better
Finding Skim encouraged me to quickly look around to see what else in this sphere might
improve my productivity. I quickly came across Atuin, which is a much
more sophisticated shell history recording mechanism: the video on its front
page showed a much nicer matching UI than I had previously considered possible.
However, I quickly realised Atuin wasn’t for me or, at least, wasn’t easily for
me. These days I regularly ssh
into many different servers: over time I’ve
streamlined my shell configuration to a single .zshrc
file that I can scp
over to a new machine and which instantly makes me productive. Atuin – and
this isn’t a criticism, because it’s a more powerful tool – is more difficult
to install and setup (I’m also not sure the ‘fuzzy’
aspects of Atuin quite match the heights of fzf/skim). That said, some readers may find
it a useful tool to investigate.
However, what I immediately realised from the Atuin video is that I would like
my fuzzy matcher to show me more useful information about the commands it’s
matching.
In particular, fzf and skim both default to showing me a (to me!) meaningless
integer before my matched command: this had always slightly bothered me, but
I’d never thought to work out what it meant. For example, if I use
zsh + fzf + Ctrl-r
I see:
What does 5408 mean and why is it taking up valuable screen space?
22 Comments
SrslyJosh
> What does 5408 mean and why is it taking up valuable screen space?
For anyone who's not familiar with something as basic as the history command, please pick up a book on bash/zsh/sh and take the time to learn.
If you don't know how to use the shell, you will struggle with (or simply be unable to perform) many basic tasks that could be accomplished quickly and easily by someone who's taken the time to learn how to use the shell and a handful of basic tools like grep, sed, awk, etc.
yjftsjthsd-h
> and I’ve come across more than one paid professional who doesn’t use the “up” key to retrieve the previous command.
Well. I prefer ctrl-p personally, but I take your point:)
northisup
I love fzf but no need to invent the wheel here if you are using zsh. check out one of these zle plugins. (Demo https://asciinema.org/a/155704)
I prefer these two, you get good performance, search that is semi-shell syntax aware, ranking that takes age somewhat into account, and syntax hilighting.
https://github.com/zdharma-continuum/history-search-multi-wo… with https://github.com/zdharma-continuum/fast-syntax-highlightin…
or the same thing but older, for those who like older things because they have an air of stability about them: https://github.com/zsh-users/zsh-history-substring-search
csdvrx
Ctrl-R and sorting by time since the last run is nice, but odds are you run the same commands again and again WITHIN the context provided by the current directory, so the cwd should be kept
Another important parameter is the number of successful runs: if you successfully ran the same command in the same directory 30 times in the last 30 days, it may be more useful that the one you have only run 1 time yesterday, so the exit value (like `false ;echo $?`) is another thing to keep
I have 2 bash shortcuts: one for a context-free search (Ctrl-T), while the default (Ctrl-R) is restricted to the current directory, and I throw everything (cwd, start time, stop time, exit value…) in a sqlite database to do more advanced queries (ex: on average, at which hour do I make more mistakes?)
eating555
Bro, just use fish shell. They have done all the heavy lifting for you.
3r7j6qzi9jvnve
> I’m also not sure the ‘fuzzy’ aspects of Atuin quite match the heights of fzf/skim).
This. I've been using atuin for a few months and this is so horrible how much better it could be with a "real" fzf matching…
I just tried skim shell integration ( https://github.com/skim-rs/skim/tree/master/shell ) and it's great.
I kind of like the extra metadata atuin saves (cwd, session context), but I think I was just waiting to stumble into this to turn atuin back off…
jakebasile
I use Atuin, but unlike the author actually find it more valuable since I use it across many machines. I have 7 different machines (of various make and OS though all *NIX) in my house right now that I regularly SSH in to or use directly, and Atuin's sync keeps my history on all of them. If I want to narrow down by host I can still do that. I also keep similar directory structures so I can narrow down by directory even if I performed a command on a different machine most of the time.
That said, some form of advanced history search is a game changer, no matter how you get it. It's one of those "can't understand how I lived without it" things once you get it going.
devjam
I've been using McFly [1] recently, and like it a lot.
> McFly replaces your default ctrl-r shell history search with an intelligent search engine that takes into account your working directory and the context of recently executed commands. McFly's suggestions are prioritized in real time with a small neural network.
[1] https://github.com/cantino/mcfly
joshka
> but I’ll often run a tiny subset of those commands (e.g. cargo test) hundreds of time in a single day.
Bacon replaces that command entirely for me (and many other cargo commands which would otherwise be run repetitively). https://dystroy.org/bacon/
Hobadee
I use zsh-histdb, and it's amazing. It shows return statuses and which directory the command was run from.
I honestly can't remember the entire setup, but my dotfiles that have the setup are here:
https://github.com/Hobadee/dotfiles/blob/master/Common/profi…
hello_computer
I usually only care about commands from the same session, but sometimes I need to delve into the global history. With the default bash setup on most distros, the history log isn't updated until the shell terminates–leaving the "global" history incomplete until the other shells terminate. That is, I can lookup history from "dead" shells, but not from "living" ones.
I think shell history would be more practical if it appended to the history log after each command (rather than at the end of the session), and tagged each line with some form of "session ID"–allowing us to distinguish between "local" and "global" history.
You can force a flush to the log "as-is" with things like prompt overrides, but that's an all-or-nothing solution, saddling us with history from foreign shells even when we don't want it. Adding a session ID to the history log, and another flag to the built-in "history" command (to toggle between local and global history), would be a worthwhile improvement.
m1keil
ctrl+r with fzf and zoxide for quick dir jumping is all I need in the shell nowadays.
throwmazacx5
LaTeX returns .tex file compilation bugs as .log files when using pdflatex [filename.tex]
Is synthetic format prior to redirection and concatenating.
Whether using /.bashrc/ or saved in local directories, when debugging is typically resolved with querying X: quit or H: help.
fsckboy
this sort of topic illustrates the type of vexing problems that I wish a community model was better at addressing. Chasing and solving your own pain points is cool but frequently creates new pain points for other people. I love more features for searching history, but…
the reason bash uses Ctrl-r for searching back in history is because that's EMACS for searching back in your edit buffer (like C-a, C-f, C-b, C-e, etc are emacs cursor motions). I like the features of fzf, but I hate that it suddenly changes muscle memory that I rely on that is bigger than the immediate context.
these kinds of problems show up all over. I have always relied heavily on "middle button pastes the current mouse selection". I don't know what's going on in linux–is it Wayland?–but that feature is increasingly and increasingly being broken in more and more places. one place (which may entail a different "feature creep") is "click the url-bar in the browser and it auto-selects the whole thing…uh uh uh, wait, but not anymore for the purposes of the X-selection. but you can get the X-selection back if you click again (to deselect the whole thing) then again to reselect the whole thing… or is it double click…" the mind boggles
the old way (or goal) was to have the UI be standard, everywhere used the same code. but there were always people who wanted to innovate on their own, then with the idea of UX it wasn't any longer about standardization–or ironically personalization–at all
I realize whoever are making these changes simply don't work the way I have (for the last 35 yrs (well, that's just with linux)) so they don't notice what's being thrown away. I guess it's getting close to that time I should climb on and drift away on a little ice raft…
larusso
I have a similar setup. I use fzf plus a unique shared history that is 10000 items long. Last time I moved machines one of the important things to take over was my shell history. I have old obscure invocations that I can‘t remember but know that I did them in the past ;)
Unique is the key for me. I don‘t care that I ran cargo test 20 times in a row. Shared history across shells is a bit polarizing depending on your workflow. For me it’s important to have access to the items I ran in a different window/tab.
But what I can‘t understand is why so many paid professionals stick to the defaults. I shows my setup a few people and they all went: „what ?!? That is possible“ And that is the end of it. Well most devs I know sadly don‘t even know how to use the default search. The fact that you have to hit CTRL-R multiple times to skip to the next search result is a mystery.
Maybe someone needs to package a shell where this kinds of options are just the default. And maybe adds a more modern key binding interface.
joshka
> The network aspects of Atuin also gave me the heebie-jeebies. Reasonable people can differ on such matters.
The networking stuff can be fully compiled out of Atuin. The client and sync feature flags[1] control this, and it's fairly trivial to confirm that the networking crate (reqwest) is only enabled when the sync feature is enabled.[2]
Atuin can also be configured to only talk to your own sync server rather than the Atuin run server, and the history is e2e encrypted, so :shrug:. There's enough good engineering there that I'd not put it as particularly high on my list of security risks.
[1]: https://github.com/atuinsh/atuin/blob/e8df3d168e7fb9d6f99d97…
[2]: https://github.com/atuinsh/atuin/blob/e8df3d168e7fb9d6f99d97…
wodenokoto
Things I'd like added to my command history:
– where it was executed
– If it failed
– stickies/favourites
v3ss0n
ReSH or McFly does the job.
worldsayshi
What I'd really like is integration between fzf and bash autocompletion so that I can get more of a code completion like experience. Or maybe something smarter still. I feel I could get so much more value from kubectl and/or yq with a bit better ux in this regard.
Maybe skim does this? Or fzf does it if I configure it differently?
Ah, found this now, looks really promising!
https://github.com/Aloxaf/fzf-tab
(Oh, zsh specific)
bramgn
No one using hstr? I'm a big fan of that one, though i must admit i haven't used much else other than bash's reverse history search.
shellwizard
At $WORK most of the machines are running KSH and I ended getting used to the vi shortcuts rather than the Emacs style, so I have also turned to it on my personal computers. Just append `set -o vi` to the user .profile or .bashrc file, and then you can use all of the vi key commands: /|? for searching, n to search previous results, etc.
thiht
If you install atuin to give it a try, be careful to backup your history first. I installed it once and it deleted all my zsh history for some reason. I've rarely been more aware of how much I rely on my history to get things done.
Also it made atuin useless to me since I didn't have a history anymore so couldn't even try it properly.