Control-R, my favorite Unix shell command

May 18, 2014 . By Reuven

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.

 

Related Posts

A quick intro to the Unix “find” utility

A quick intro to the Unix “find” utility

My new course, “Understanding and Mastering Git,” is now available

My new course, “Understanding and Mastering Git,” is now available

A very sad day — the end of Linux Journal

A very sad day — the end of Linux Journal
  • For 99% of actual use cases in the past 15 years, a simple bang (plus up/down arrow keys) covers most of my reverse-history needs. That is:

    $ !prefix

    The more “advanced” your command line heroics will get, the faster the day will come when you will destroy a system with a wrong history item.

  • Be warned that pressing CTRL-S in PuTTY will send XOFF (pause transmission) and on some systems your terminal will apparently freeze. Press CTRL-Q for XON .(resume transmission).

  • I use this one, does exactly the same but more much more quickly. Uncomment this two lines in your /etc/inputrc file:

    # alternate mappings for “page up” and “page down” to search the history
    “\e[5~”: history-search-backward
    “\e[6~”: history-search-forward

    There you have, a FAR more better method to accomplish that. Source the file or logout and login again. Now you can search and reverse search any command in your .bash_history by just typing one or two chars and pressing page-up/page-down.

    Cheers!

  • Even easier is to have PageUp and PageDown mapped in your /etc/inputrc to do the history reverse search. On Ubuntu, these are set up but commented out by default. Here’s what the lines in /etc/inputrc should look like:

    # alternate mappings for “page up” and “page down” to search the history
    “\e[5~”: history-search-backward
    “\e[6~”: history-search-forward

    From memory, I think Fedora and derivatives have these uncommented already. So no need to hit ctrl-r, just type the first few characters of the command you want, then PageUp to recall it.

  • $ cat .inputrc

    “\e[A”: history-search-backward
    “\e[B”: history-search-forward

    Something way better than Ctrl + R. Simply add those lines to your ~/.inputrc, restart your shell, then type the first few letters of some command and navigate up and down within your filtered history now. Try it. You will never use Ctrl + R. Ever again.

  • Great article..
    I do not know Emacs, but I do know vi, and one can use VI to do this also.

    To enable vi mode:
    set -o vi

    Then, to search backwards:
    ?ssh

    use the n for next till you find the one you want.

  • ^R is definitely one of the handiest things in bash. maybe’s comment is quite eye opening … *goes off to study more custom configuration for bash*

    • CTRL+R is wonderful, but it’is better to keep a separated bash ethernal history, just in case:

      export HISTTIMEFORMAT=”%h/%d/%y – %H:%M:%S ”
      export HISTCONTROL=ignoreboth
      export HISTSIZE=5000
      shopt -s histappend
      PROMPT_COMMAND=”${PROMPT_COMMAND:+$PROMPT_COMMAND ; }”‘echo $$ $USER “$(history 1)” >> ~/.bash_eternal_history’

  • Cool article! Thanks for sharing this, not only did I learn about Control-R/S which are very useful, but I didn’t know that I could modify the size of my Bash history!

  • Ian Taylor says:

    Awesome, I didn’t know about this command!

    Also with oh-my-zsh you can type part of the command and hit the up or down arrow to search through the history, without having the go into control-r, this is extremely useful. I find myself hardly ever typing full
    commands now, I just type “ssh” and hit the up arrow to find the connection I want (if I have typed it before of course).

  • I’m a big fan of reverse-search-history too. So much so that I miss it in the Firefox console.

    I also sometimes add tagging comments to hard-earned commands, e.g.


    git log --perl-regexp --grep="\b(?i:FIX).*\d+" # ignore case

    Here are some of my additional settings from .bashrc, which help me avoid losing history, etc.


    # don't put duplicate lines in the history. See bash(1) for more options
    export HISTCONTROL=ignoredups
    # ... and ignore same sucessive entries.
    export HISTCONTROL=ignoreboth
    export HISTTIMEFORMAT='%Y%m%dT%H%M%S%z '
    # Free up forward-search-history (C-s) in bash.
    stty stop ^J
    # Append to the history file instead of overwriting.
    shopt -s histappend
    # Append unsaved commands to history file.
    PROMPT_COMMAND="history -a;$PROMPT_COMMAND"

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >