1. Overview

An important functionality of shell programs is to retain the user’s previous commands as history. In this article, we’ll study the searching functionalities that GNU Bash offers and also learn of a conflicting key sequence (C-s) used by both the terminal and the shell.

2. Incremental Searching

The GNU Bash shell provides a command-line editing ability via the Readline library. This library includes a couple of history searching functions, reverse-history-search and forward-history-search, that are bound to the C-r and C-s key sequences, respectively.

We can confirm this by using the builtin command bind in bash:

bind -q reverse-search-history;
# reverse-search-history can be invoked via "\C-r".
bind -q forward-search-history;
# forward-search-history can be invoked via "\C-s".

These search functions can be used to fetch an earlier command and restore it into the current line. They also feature immediate feedback with each character’s input. This is known as incremental searching.

Let’s create some shell history data in bash and demonstrate a simple search experience:

unset HISTFILE # if variable HISTFILE is unset, then ~/.bash_history will not be modified.
history -c
: a;
: b;
: c;
: d;
: e;

Now, let’s search for the command ‘*: e;‘ in our history. To do this, we simply enter the key sequence C-r to invoke the search function and enter the character ‘e*‘:

(reverse-i-search)`e': : e;

There is a small gotcha with bash’s incremental search: It persists its position in the history list and does not wrap after a failed search. This behavior can be observed when we try to search for a command while we’re at an earlier position in the history list:

(reverse-i-search)`d': : d;
(failed reverse-i-search)`e': : d;

In order to find ‘*: e;’* without restarting the search completely, we’ll need to invoke the function forward-history-search using the C-s key sequence. Unfortunately, the C-s will most likely be intercepted and consumed before the shell even receives it.

We’ll learn about another system that also uses the key sequence C-s in the next section, but for now, we should abort the search by sending the key sequence C-g.

2. Software Control Flow (XOFF/XON)

Historically, C-s and C-q were used to communicate the control codes XOFF and XON between the two systems. Within a terminal-based system, these control codes are used to suspend processes that are transmitting data to the terminal.

Let’s open a shell and observe that C-s and C-q can be used to stop/resume the loop construct as the loop body requests data to be written on the terminal:

for i in 1 2 3 4 5; do
    printf "%s\n" $i; sleep 1;
done;

Fortunately, it’s possible to free the key sequence C-s from sending the control code XOFF and let the key sequence arrive at the shell successfully. The stty utility can be used to configure the input settings ixon, which enables XOFF/XON flow control. Let’s first confirm the current settings, before disabling ixon:

stty -a | grep "ixon"
# -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
#                                                                    ^^^^
stty -ixon
stty -a | grep "ixon"
# -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -ixon -ixoff
#                                                                    ^^^^^

The key sequence C-s should now successfully invoke the bash shell’s forward-search-history function:

(i-search)`':

These settings will not persist, though. We’ll need to configure them with each new shell session. In order to persist these settings, we have to include the command stty -ixon in our shell’s startup file.

3. Switching Search Directions

When we invoke the forward-search-history function, the search starts from the current position, which is effectively the last position in the history list. As a result, it’s more relevant to use forward-search-history as a complement to the reverse-search-history function, which effectively moves back the search position as it searches.

Let’s create a new data set for our command history to demonstrate a typical usage of forward-search-history:

unset HISTFILE # if variable HISTFILE is unset, then ~/.bash_history will not be modified.
history -c
: a; 
: b;
: x1;
: d;
: x2;
: e;

Now, let’s search for the command ‘*: x1;‘ that we ran earlier. We can do this by simply entering the key sequence C-r to invoke the search function and entering the characters ‘x1*‘:

(reverse-i-search)`x1': : x1;

Suppose we want to continue our search, but for a command which was run at a later time. We can simply enter the key sequence C-s to change the search’s direction, and then, correct the search terms:

(i-search)`x2': : x2;

Finally, we can terminate the search and insert the search results into the current line by sending the ESC key or the key sequence C-j.

4. Conclusion

In this article, we’ve studied GNU Bash’s incremental search mechanisms. We’ve also configured the terminal line setting XON/OFF flow control, which was causing our key sequence C-s to be intercepted in advance.