Control-R, my favorite Unix shell command

If you use a modern, open-source Unix shell — and by that, I basically mean either bash or zsh — then you really should know this shortcut. Control-R is probably the shell command (or keystroke, to be technical about it) that I use most often, since it lets me search through my command history.

Let’s start with the basics: When you use bash or zsh, your commands are saved into a history, typically put in the environment variable HISTFILE. I use zsh (thanks to oh-my-zsh), and it puts my HISTFILE in ~/.zsh_history. How many commands does it store? That depends on the value of the environment variable HISTSIZE, which in my case is 10,000. Yes, I store the 10,000 last commands that I entered into my shell.

Now, before control-R, there were a bunch of ways to search through and use the history. Each command has its own number, and thus if you want to replay command 5329, you can do so by typing

!5329

But this requires that you keep track of the numbers, and while I used to do that, I found it to be more annoying than useful. What I really wanted was just to repeat a command … you know, the last time I ssh’ed into a server, or something. So yeah, you can do

!?ssh

and you’ll get the most recent “ssh” command that you entered. But what if you have used ssh lots of times, to lots of servers? You could start to search for the server name, but then things start to get complicated, messy, and annoying.

What control-R does is search backwards through HISTFILE, looking for a match for what you have entered until now. If you use Emacs, then this will make perfect sense to you, since control-R is the reverse version of control-S in Emacs. If you don’t know Emacs, then it’s a crying shame — but I’ll still be your friend, don’t worry.

Let’s say you have ssh’ed into five different servers today, and you want to ssh again into the third server of the bunch. You type control-R, which puts you into bck-i-search (i.e., “backward incremental search”) mode. Now type “s” (without enter). The most recent command that you entered, which contains an “s”, will appear. Now type another “s” (again, without pressing enter). The most recent command containing two “s” characters in a row will appear. Depending on your shell and configuration, the matching text might even be highlighted.

Now enter “h”. In my case, I got to the most recent call to “ssh” that I made in my shell. But I don’t want this last (fifth) one; I want the third one. So I enter control-R again, and then again. Now I’m at the third time (out of five) that I used ssh today, at the command I want. I press “enter”, and I’ve now executed the command.

While searching backward, if you miss something because you hit control-R one too many times, you can use control-S to search forward. You can use the “delete” key to remove characters, one at a time, from the search string. And you can use “enter”, as described above, to end the search. I should also note that I’ve modified my zsh prompts such that the matched text in control-R is highlighted, which has made it even more useful to me.

So, when was the last time I entered the full “ssh” command into a client’s server? I dunno, but it was a while ago… since the odds are that within the 10,000 most recent commands, I’ve got a mention of that client’s server. And if I needed to pass specific options to ssh, such as a port number or a certificate file to get into AWS, that’ll be in the history, too. By combining a huge history with control-R, you can basically write each command once, and then refer back to it many times.

Now the fact is that control-R isn’t really part of bash or zsh, per se. Rather, it’s part of a GNU library called “readline” that is used in a large number of programs. For example, it’s used in IPython, Pry, and the psql command-line client for PostgreSQL. Everywhere I go, I can use control-R — and I do! Each program saves its own history, so there’s no danger of mixing shell commands with PostgreSQL queries.