Notes on Bash

Sun, 15 Jan 2012 00:00:00 GMT

At some point I wanted to learn more about Bash's command history facility. Here are some notes I made about this and other things I found.

History

Bash's history facilities are described in Chapter 9 of the Bash manual (PDF).

The History File

Bash keeps track of the previously issued commands. When Bash starts, it reads the command history into memory from a file, typically ~/.bash_history, and makes those commands available via the up arrow, etc. (see below).

When Bash terminates, it writes the last $HISTSIZE commands into the history file. The file gets overwritten by default. This means that if you open two shells, issue some commands in the first, close that, issue some commands in the second and close it, the commands issued in the first shell instance will not be in the history file, as they have been overwritten when the second shell closed.

If we do not want to save only the commands from the last exiting shell, Bash provides the histappend option. When this option is set, commands are appended to the history file instead of overwriting it when the shell is closed:

shopt -s histappend

That might be worth adding to ~/.bashrc. (Debian and Ubuntu seem to do this by default.)

We might also want to increase the number of commands saved (it was set to 500 by default on my system):

export HISTSIZE=10000
export HISTFILESIZE=10000

Using the History

The history command can be used to inspect and modify the contents of the command list:

history                 # Shows the history list.
history -c              # Clears the history list.
history -d <line>       # Delete a specific history line.

The most basic way of using the command history is to use the up and down arrow keys (or CTRL-p and CTRL-n) to select a previous command.

Another way is to use the exclamation mark (the history expansion character):

!foo            # Runs the most recent command starting with "foo"
!!              # Runs the previous command
!n              # Runs command line n
!-n             # Runs to the nth previous command (!! equals !-1)

Those are called event designators. We can combine an event designator with a word designator to access only part of a previous command, and maybe reuse it in another one. For example, we might do something like this:

vim /some/file           # Edit a file.
git add !$               # Add that file to the git index.
git commit -m "Message"  # Commit.

In the example above, !$ gets expanded to the last argument of the previous command, i.e. /foo/bar/some/file.

We put a : after an event designator and use a word designator to select a specific part of a command. Some of the word designators are:

n              # The nth word (0 is typically the command name)
^              # The first argument (i.e. 1)
$              # The last word
x-y            # Words x to y
*              # All words except the first

So to get the third word of the previous command, we can use !!:3.

The colon can be omitted if the word designator starts with ^, $ or *. If we do not provide an event designator, the previous command is used as the event. This allows for shortcuts as:

!$      # The last argument of the previous command
!^      # The first argument of the previous command
!*      # All arguments of the previous command

Incremental Search

Another, more interactive, way of using the history is to use incremental search.

Start searching with CTRL-r. Bash will search the history as you type. Press CTRL-r again to go to an earlier match, and CTRL-s for a more recent match.

I use this all the time. For example, rather than doing !make to execute the previous make command, I tend to do CTRL-r make, which lets me inspect the command before executing it. If I then realize that I actually wanted the second-to-last make command, I can just press CTRL-r again to get it.

I/O Redirection

This is described in Section 3.6 of the Bash manual.

It is relatively easy to remember that > file is used to redirect stdout to file by overwriting it, and that >> file appends to the file.

To redirect stderr instead, we put the number of that file descriptor (2) in front of the operator: 2> file and 2>> file.

The one that is harder to remember is how to redirect one stream to the other: 2>&1 will redirect stderr to stdout.

To redirect both stderr and stdout to the same file: >file 2>&1. Note that it is easy to get this backwards.

Command Line Editing

This is described in Chapter 8. Bash uses a library called Readline for this. The same library is used in other applications as well, so these commands are useful beyone Bash.

CTRL-_ | Undo
CTRL-e | Move to end of line
CTRL-a | Move to start of line
CTRL-f | Move forward one word
CTRL-b | Move backward one word
CTRL-k | Kill the text until end of line

My favourite: fc ("fix command") opens the previous command in $EDITOR and then runs it after editing.

Miscellaneous

For-loops

I can never remember where to put the semicolons in a Bash for-loop. They are described here, and are supposed to look like this:

 for name [ [in [words ...] ] ; ] do commands; done

Process Substitution

Process substitution is handy when diffing the output of two commands. For example:

diff -u <(gcc -S -O2 -o - a.c | head) <(gcc -S -O3 -o - a.c | head)