Bash Command Line Editting and History

Part of the book: Bash: The Linux Command Line

In Bash the text on the command line can be edited in place using cursor keys, backspace and delete. Bash also retains a history of entered commands so you can easily reuse or edit previous commands and use all or part of previous command lines in new commands. The simplest way of accessing history is using the up and down arrow key, then using left, right, backspace and delete to edit the line.

This normally works quite well if you are using a decent terminal emulation. This is true for the Linux console and an X-Windows terminal emulator, but you may have problems if you are trying to use a Windows terminal emulation program or a clunky old serial terminal. I'm not going to to into these at this time because not may clunky old terminals were spared from landfill and if you're using Windows you can just download Putty and get a decent emulator.

Editing Keys

The keys for editing are simple. If all is set correctly you can use your cursor keys. Up goes to the previous issued command, down to the next command. Left and Right move between characters in the displayed command so that you can selectively edit characters. Delete and Backspace should work as expected as should Home and End.

In fact it's so simple that mentioning this could be insulting, so I'm sorry.

But if in fact you don't have cursor keys working you can use the Ctrl-P for previous issued command and Ctrl-N for the next command. Ctrl-F and Ctrl-B stand in for cursor right and left. Ctrl-H should work for backspace and delete.

If you don't like these you can map your own keys using the bind built-in command.

Displaying History

You can display the entire history of lines you've issued using the history built-in command:

history

This will show a enumerated list of all the previous commands.

You can also selectively list history using the fc command:

fc -l -20

The above shows the last 20 commands.

Searching history

There are several ways to search through history to find certain commands. The easiest way to search history is using the reverse-search-history function. To access it press Ctrl-R, you will be prompted for a search string, enter a search string, either part of a command or an argument. The most recent matching entry will automatically appear as you type. If the most recent isn't what you want you can press Ctrl-R again to select the next most recent matching entry. When you see the command you want use cursor keys to edit the entry if you like or press Enter to execute the command. So what if you go too far and want to forward through matching entries. Well that's complicated. There is a simple keystroke that does this, Ctrl-S, but if you try it won't work. So we can find out why it doesn't work or we can change it to another keystroke. That's right, it's all configurable.

Ctrl-S Problem

Ctrl-S happens to be a hold-over from the days of low-speed serial communications. It was a special code that would tell the remote end of the serial line to stop sending characters. It was a way to prevent lost characters. It was implemented in the tty serial driver so it's essentially the kernel that "eats" the code. Today, we have hardware consoles and network connections so we can do away with this character. Now if you ever access your system from an old serial cable or telephone modem (and I don't mean a DSL or cable modem) you might want to reconsider this change.

To prevent Ctrl-S from being eaten by the tty driver run this command:

stty stop ''

If you want this to be a permanent change you will have to add this to your .bashrc file.

After that change the tty driver will pass the Ctrl-S to the process and your forward-history-search function will work.

Another way to deal with this is to bind a new key to the function. We use the bind command along with the official name of the function forward-history-search. In this example we'll bind Ctrl-B (cursor left) to the function since we can use cursor keys to move the cursor.

bind '\C-b':forward-search-history

If we want to make this permanent we would need to add it to your .bashrc file.

Now you can use Ctrl-B to search forwards through history.

History Expansion

This next section is pretty intense so if you think you have all you need to use Bash history ignore this section. If you want to become a command line king you'll need to keep reading there are some real time savers here if you're willing to commit some of these keystrokes to memory.

Another way to use history is through expansion. This means use special codes to insert previous commands, or parts of them, into the current line and optionally to change them during substitution.

The simplest form of history expansion is an Event Designator. It starts with an exclamation mark (!) and has many forms:

Event Designator Example Description
!! !! Substitute the last command line in full
!n !123 Substitute command line 123 from history. The number comes from the listing from either the histOry or fc command.
!-n !-3 Substitute the third last command line from history.
!string !vi Substitute the last command that begins with vi.
!?string? !?recipe? Substitute the last command line which contains the word .
!# !# Substitute the current command line so far.
^string1^string2^ ^needle^haystack^ Substitute the previous command but replace needle with haystack

We can use one of the substitutions anywhere in a command line. Typically it's used to re-issue commands and often they are used on a blank line:

!!

This would simply re-run the previous command. This doesn't sound as easy as simply pressing Up cursor and Enter. But consider that you want to re-issue a line from a long time ago, but you know it was the last time that command was used. It could be really handy to re-issue the last of a specific command:

!man

That would run the last man command. Be careful though it will also match a command called "mangle" it matches beginning of line. It's usually pretty safe though since you would have had to issue a "mangle" command after the last man command.

If we want to echo the previous line it would be simple:

echo !!

The result of the above would be that the previous command would be displayed on the screen.

This also shows one way to test history expansion, by echoing the history you are trying to substitute. Another way is to press Esc ^. This key sequence will expand the history on the command line without executing it.

A more useful example of expansion is:

logger "Finished: !!"
sudo tail /var/log/messages

The above would log the previous line to the system logs. The sudo tail command runs tail as root and lists the last few lines of the /var/log/messages file. You should see your last command listed in the log file.

Advanced Substitition

We can also grab just a portion of a previous command line, this syntax is called Word Designators and allows one to extract words from a previous command you and substituting them into the current command. This is really useful if you are working on large arguments, whether that is a command name or file name, or a list of complex arguments.

Word designators always follow an Event Designator (see above) but are separated from them with a colon (:). They apply to the command line selected by the Event Designator. So if we wanted to pull words from the last line we would start with !!: and add one of the following word designators:

Word Designator Example Description
0 !!:0 Substitute word 0 from the last command line (i.e. the command from the previous line)
n !!:2 Subtitute word 2 from the last command line
$ !!:$ Substitute the last argument from the last command line
% !?hel?:% Substitute the most recent word found in history that contains the text hel.This really works by substituting the last word searched for with !?string?. The search doesn't need to be on the same line. In other words once you've searched once with !?string? you can use !% over and over and it will give the same result.
n-m !!:2-4 Sustitute words n through m. You can abbreviate 0-n to -n.
* !!:* Substitute arguments 1 and on.
n* Substitute arguments n and on.
n- Substitute arguments n to the second last

There are some interesting uses of this. Let's say you used a very long file name that has a unique name, recipe. To verify you could search for this name without executing the command by putting the substitution after an echo command:

# Sometime before you ran
#     vi /home/john/documents/recipes/southwest/quacamole_dip.txt
#
echo !?recipe?

Once found the !% will now work until the next search. That means you can use the argument easily in another command:

lp !%

That command would result in this string lp /home/john/documents/recipies/southwest/quacamole_dip.txt and would print the file only you typed a lot less.

You can also re-issue the same command arguments over and over. Lets say you just looked into a file and decide it's in the wrong directory:

vi /home/john/documents/recipes/southwest/poutine.txt
mv !$ /home/john/documents/recipes/canadian

As you can see !$ and !% are shortforms for !!:$ and !!:%.

Modifiers

We can also apply a modifier to the selected history. This is handy for doing slight alterations to substitute text.

Modifier Example Description
h !:1:h Substitute only the head of the file path. i.e. remove the trailing element of the path. E.g. /home/john/.bashrc becomes /home/john
t !:1:t Substitute only the tail of the file path. i.e. remove all but the last element of the path. E.g. /home/john/.bashrc becomes .bashrc
r !:1:r Remove the trailling file suffix. i.e. remove everthing after the last dot in the file name. e.g. /home/john/resume.txt becomes /home/john/resume
e !:1:e Leave only the trailing file suffix. e.g. /home/john/resume.txt becomes .txt
p !:p Substitute but don't execute, only print.
q !*:q Quote the substituted words with single quotes to avoid expansion. To see this in effect try:
echo $PATH $PATH
echo !*:q
x !*:x Quote the substituted words at word breaks (spaces, newlines). To see this in effect try:
echo $PATH $PATH
echo !*:x
s/old/new/ !?poutine?:s/southwest/canadian/ Substitute new for the first occurrence of old in the event
line. Any delimiter can be used in place of /. The final
delimiter is optional if it is the last character of the event
line. The delimiter may be quoted in old and new with a single
backslash. If & appears in new, it is replaced by old. A single backslash will quote the &. If old is null, it is set to
the last old substituted, or, if no previous history substitutions took place, the last string in a !?string[?] search.
& !-5:& Repeat the last substitution. (presumably on another line)
g !?images?:gs/.jpeg/.jpg/ Cause changes to be applied over the entire event line. This is
used in conjunction with ‘:s’ (e.g., ‘:gs/old/new/’) or ‘:&’.
If used with ‘:s’, any delimiter can be used in place of /, and
the final delimiter is optional if it is the last character of
the event line. An a may be used as a synonym for g.
G !?images?:Gs/mexico/Mexico/ Apply the following ‘s’ modifier once to each word in the event
line.