What's GNU: Bash—The GNU Shell

Conclusion of an article started last month. While originally written by Brian Fox of the Free Software Foundation, bash is now maintained by Chet Ramey. In this article, Chet explains the history of shells and then goes on to explore features specific to bash.

Access to the list of commands previously entered (the command history) is provided jointly by bash and the readline library. bash provides variables ($HISTFILE, $HISTSIZE, and $HISTCONTROL) and the history and fc builtins to manipulate the history list. The value of $HISTFILE specifies the file where bash writes the command history on exit and reads it on startup. $HISTSIZE is used to limit the number of commands saved in the history. $HISTCONTROL provides a crude form of control over which commands are saved on the history list: a value of ignorespace means to not save commands which begin with a space; a value of ignoredups means to not save commands identical to the last command saved. $HISTCONTROL was named $history_control in earlier versions of bash; the old name is still accepted for backward compatibility. The history command can read or write files containing the history list and display the current list contents. The fc builtin, adopted from POSIX.2 and the Korn Shell, allows display and re-execution, with optional editing, of commands from the history list. The readline library offers a set of commands to search the history list for a portion of the current input line or a string typed by the user. Finally, the history library, generally incorporated directly into the readline library, implements a facility for history recall, expansion, and re-execution of previous commands very similar to csh (“bang history”, so called because the exclamation point introduces a history substitution):

$ echo a b c d e
a b c d e
$ !! f g h i
echo a b c d e f g h i
a b c d e f g h i
$ !-2
echo a b c d e
a b c d e
$ echo !-2:1-4
echo a b c d
a b c d

The command history is only saved when the shell is interactive, so it is not available for use by shell scripts.

New Shell Variables

There are a number of convenience variables that bash interprets to make life easier. These include FIGNORE, which is a set of filename suffixes identifying files to exclude when completing filenames; HOSTTYPE, which is automatically set to a string describing the type of hardware on which bash is currently executing; command_oriented_history, which directs bash to save all lines of a multiple-line command such as a while or for loop in a single history entry, allowing easy re-editing; and IGNOREEOF, whose value indicates the number of consecutive EOF characters that an interactive shell will read before exiting—an easy way to keep yourself from being logged out accidentally. The auto_resume variable alters the way the shell treats simple command names: if job control is active, and this variable is set, single-word simple commands without redirections cause the shell to first look for and restart a suspended job with that name before starting a new process.

Brace Expansion

Since sh offers no convenient way to generate arbitrary strings that share a common prefix or suffix (pathname expansion requires that the filenames exist), bash implements brace expansion, a capability picked up from csh. Brace expansion is similar to pathname expansion, but the strings generated need not correspond to existing files. A brace expression consists of an optional preamble, followed by a pair of braces enclosing a series of comma-separated strings, and an optional postamble. The preamble is prepended to each string within the braces, and the postamble is then appended to each resulting string:

$ echo a{d,c,b}e
ade ace abe
Process Substitution

On systems that can support it, bash provides a facility known as process substitution. Process substitution is similar to command substitution in that its specification includes a command to execute, but the shell does not collect the command's output and insert it into the command line. Rather, bash opens a pipe to the command, which is run in the background. The shell uses named pipes (FIFOs) or the /dev/fd method of naming open files to expand the process substitution to a filename which connects to the pipe when opened. This filename becomes the result of the expansion. Process substitution can be used to compare the outputs of two different versions of an application as part of a regression test:

$ cmp <\>(old_prog) <(new_prog)
Prompt Customization

One of the more popular interactive features that bash provides is the ability to customize the prompt. Both $PS1 and $PS2, the primary and secondary prompts, are expanded before being displayed. Parameter and variable expansion is performed when the prompt string is expanded, so any shell variable can be put into the prompt (e.g., $SHLVL, which indicates how deeply the current shell is nested). bash specially interprets characters in the prompt string preceded by a backslash. Some of these backslash escapes are replaced with the current time, the date, the current working directory, the username, and the command number or history number of the command being entered. There is even a backslash escape to cause the shell to change its prompt when running as root by using the su command. Before printing each primary prompt, bash expands the variable $PROMPT_COMMAND and, if it has a value, executes the expanded value as a command, allowing additional prompt customization. For example, this assignment causes the current user, the current host, the time, the last component of the current working directory, the level of shell nesting, and the history number of the current command to be embedded into the primary prompt:

$ PS1='\u@\h [     ] \W($SHLVL:\!)\$ `
chet@odin [21:03:44] documentation(2:636)$ cd ..
chet@odin [21:03:54] src(2:637)$

The string being assigned is surrounded by single quotes so that if it is exported, the value of $SHLVL will be updated by a child shell:

chet@odin [21:17:35] src(2:638)$ export PS1
chet@odin [21:17:40] src(2:639)$ bash
chet@odin [21:17:46] src(3:696)$

The \$ escape is displayed as “$” when running as a normal user, but as “#” when running as root.