Cover V12, I01

Article

jan2003.tar

Highlighting Linux Command Prompts with the PROMPT_COMMAND Variable

Kirk Becker

Most Linux installations provide a non-default command prompt startup with six available terminals accessible concurrently as {Alt-F1} through {Alt-F6}. Users can log in to explore their systems on the first terminal, then {Alt-F2} to log in again and read the man pages, then {Alt-F3} to log in again and read the HOWTOs. After reading the man pages and HOWTOs, users can {Alt-F1} back to the original terminal or {Alt-F4} to still another terminal to test a different command.

Each terminal performs {TAB}-completion based on its present working directory and maintains its own {UpArrow/DownArrow} command history. Additionally, Linux maintains a screen buffer so that users can {Shift-PgUp/Shift-PgDn} to scroll through the output of a long command or the short output of several recent commands. Unfortunately, switching between terminals clears the screen buffer.

It's not the same functionality as scroll bars and multiple windows on one screen, but the ability to scroll through the screen buffer and the availability of multiple terminals greatly improves the facility of the Linux command line on the console. However, when scrolling through the output from the past several commands in a screen buffer, it's hard to see where one command ends and the next command begins. Furthermore, it's easy to get lost in Linux with several terminals open at once. This article describes how you can implement a colored command prompt (displayed before each command) that shows which terminal is visible and makes it easy to distinguish the commands from the output.

The PROMPT_COMMAND

The PROMPT_COMMAND, per the man page, is "executed as a command prior to issuing each primary prompt". Because the PROMPT_COMMAND can be a bash script that runs another script, it can be customized fairly easily. The PROMPT_COMMAND is typically installed in a startup file, conventionally /etc/profile, ~/.profile, or ~/.bashrc. On my SuSE system, /etc/profile runs something called /etc/profile.local, followed by ~/.bashrc. In the end, I put my PROMPT_COMMAND='...' variable and PS1='...' variable that sets the command prompt in /etc/profile.local, but we'll start out in ~/.bashrc in order to localize our experiments.

First we'll make a backup of .bashrc and add a simple PROMPT_COMMAND variable to .bashrc, then we'll run the simple command through a PROMPT_COMMAND script. Finally, we'll colorize the PROMPT_COMMAND.

Before getting started, log in to the console and type "cp .bashrc bashrc.stillworks" in case you accidentally install a password-protected screensaver as your prompt command.

Open .bashrc (or whatever file contains your PS1 variable) in a text editor, add the line "PROMPT_COMMAND=tty", and save the file. Quit the text editor and type "source .bashrc" (or ". .bashrc") to run the startup script (within the same shell rather than a subshell) so you don't have to log out and log back in. Your prompt should change to something like /dev/tty1[kirksuse@battop ~].

Reopen .bashrc and find the PS1=variable line that sets your primary command prompt. Add "\n" immediately after the equals (=) sign, add a space at the end of the line, and enclose everything from the right of the equals sign to the end of a line in single quotes. (e.g., PS1='\n[\u@\h \w]\$'). Doing so will retain your PROMPT_COMMAND on its own line and keep the original prompt over on the left side, where it belongs. Source .bashrc (". .bashrc") again to effect the change.

Before we start adding color, I want to generalize the PROMPT_COMMAND to echo the output of a script. Create a text file that I call "FancyPrompt" and put the following two lines in it:

$fancyprompt1=$(tty)
echo -n $fancyprompt1
In the first line, the $(command) construct just runs the tty command, and the $var=$(command) construct sets the contents of the $fancyprompt1 variable to the output of the tty command. The echo command in the second line of the FancyPrompt script just echoes the contents of the $fancyprompt1 variable to the screen followed by a blank line, except that the -n suppresses the blank line.

Then, go back into .bashrc and change the PROMPT_COMMAND to:

PROMPT_COMMAND='echo -n $(~/FancyPrompt)'
As above, the echo command just echoes the output of the FancyPrompt script to the console, suppressing the new line normally appended by the echo command itself. The single quotes around the 'echo ...' command are necessary to keep the whole line together so that it's executed as a unit by the PROMPT_COMMAND. It may seem a little redundant to suppress the new line from the PROMPT_COMMAND's echo command and then add a blank line to the beginning of the prompt itself, but doing so is necessary to keep the colors (added below) together on the screen.

Adding Color

Most modern Linux PCs have color monitors, graphics cards, and terminal programs that recognize the ancient ANSI color codes, and the next step is to add color to the PROMPT_COMMAND to highlight it on the console. The standard graphics command is:

{ESC}[3#;4#m
The {ESC}[ string begins the color coding; 3 followed by a number (#) sign specifies the foreground color; the semicolon (;) is a separator; 4 followed by a number (#) sign specifies the background color; and the m concludes the sequence. The colors are:

0 black
1 red
2 green
3 yellow
4 blue
5 magenta
6 cyan
7 white

Thus, the normal white-on-black color scheme is {ESC}[37;40m. Incidentally, the code to reset the screen colors to "normal" is {ESC}[00m -- the difference only shows up in XTerms within X Window, where the XTerm is trying to maintain a black text on white background color scheme.

The ANSI sequences can be a little confusing because the {ESC} character is frequently displayed as ^[, so the ANSI codes sometimes show up as ^[[37;40m or ^[[00m. For a more complete description of the ANSI color capabilities, {Alt-F#} switch to another terminal, log in, and type dircolors -p | less{RETURN}.

To colorize the FancyPrompt, you simply set the PROMPT_COMMAND to echo the color change to the screen, echo the output of your FancyPrompt script, and then echo the color change back to normal as follows:

PROMPT_COMMAND='echo -en \{ESC}[34\;46m ; echo -n $(~/FancyPrompt) ;
echo \{ESC}[00m'
The above command is a little more complex than so glibly stated above.

There are three echo commands, separated by two semicolons, all within single quotes so the three echo commands are executed together as a single command. That's what I meant above when I referred to running a script (FancyPrompt) within a script (the three echo commands run together as a unit) -- at least conceptually, this is very simple. The first and third echo commands need the -e parameter to recognize the {ESC} character, and all three echo commands need the -n parameter to suppress new lines. The ; character within the first color change echo command (and third if you use {ESC}[37;40m rather than {ESC}[00m) needs to be backslash/escaped so that it isn't interpreted as a command separator before being passed to the echo command, and the {ESC} characters need to be backslash/escaped as well.

Typing the {ESC} Character into .bashrc

Some text editors might have a problem getting the {ESC} character into their .bashrc files, so try typing {Ctrl-V} before pressing escape.

If necessary, use the command line to echo everything between the single quotes to the end of your .bashrc file through a double arrow (>>) redirector. For example:

echo 'all this including \{Ctrl-V}{ESC} between single quotes' >>
.bashrc
If you only used a single redirector arrow (> .bashrc), don't worry about it -- we've all made that mistake. Just type cp bashrc.stillworks .bashrc and start over. Then use your editor to insert PROMPT_COMMAND=' before the line, and add a ' to the end of the line.

Fancifying the FancyPrompt

With the above structure in place, where the prompt command:

1. Executes a color change,
2. Displays the FancyPrompt script output, and then
3. Changes the color, followed by
4. A new line and the normal $PS1 command prompt, you can customize the PROMPT_COMMAND by simply editing the FancyPrompt script. Here's mine:

$fancyprompt1=$(tty)
$fancyprompt2=${fancyprompt1#/*/}
$fancyprompt3=$(apm -m | cut -d: -f2)
echo -n "fancyprompt2 at $fancyprompt3"
The first line just captures the output of the tty command. The second line uses the Bash shell's "parameter expansion" feature to clip the /dev/ from the front of /dev/tty# (man bash, then / search for parameter expansion until you get to the section that explains it). The third line gets the remaining battery charge status on my laptop. The fourth line just echoes the output to be displayed by the PROMPT_COMMAND.

Final Installation

Finally, the FancyPrompt script must be moved to the /etc subdirectory where it belongs, change-owned to root, and change-moded to 755 to make it writable only by root and readable and executable by all users. Then, the middle echo command within the PROMPT_COMMAND needs to be changed to show the correct path (/etc/FancyPrompt instead of ~/FancyPrompt).

Selecting a Better Font

It's hard to discuss the command line without some reference to selecting a most useful font, and modern distributions tend to hide the font-setting process. Check out /etc/init.d/kbd or /etc/rc.d/kbd for a setfont=$Variable line -- the environment variable usually comes from a config file that's sourced at the beginning of the /etc/init.d/kbd script, such as /etc/config.variables. The config.variables (or whatever) is the place to set the default startup font.

The man page for the setfont command notes that the default consolefonts subdirectory is /usr/lib/kbd/consolefonts. Change directory (cd) into the consolefonts subdirectory and note a couple of simple font names such as b.fnt or c.fnt. You may have to blindly type something like setfont c.fnt to recover the visibility of your command line, then just go through the list looking for one that fits your eyes by typing setfont eg.fnt or setfont eg.fnt.gz as appropriate. I use the lat9v-10.psfu.gz font, which provides 43 lines/screen, which is more than the default 24 and more readable than the maximum 50 lines/screen. I also had to change the console_unicodemap to a matching lat9v.uni to get it to work well. If your prompt turns to gibberish, type "reset" and press the {Enter} key.

Then go into the /etc/config.variables file to change the font and, if necessary, the unicode map variable(s). Telinit back into the console runlevel (or reboot), and you should have a Linux box with a most useful font and a fancy prompt!

Miscellanea

Instead of {Alt-F#} switching between terminals, users can usually suspend a job with {Ctrl-Z} to put the first job in the background and get a command prompt again, run a command, and then use the fg command to return to the first "job". That's occasionally useful, too. My experience is that adding the PROMPT_COMMAND to /etc/profile.local will enable it on the console and usually in XTerms within X Windows, and that adding the PROMPT_COMMAND to ~/.bashrc on a remote computer will enable it within XTerms across a network through ssh.

In your backups, keep a copy of the FancyPrompt script, and also a FancyPromptPrompt file containing the contents of your PROMPT_COMMAND and PS1 variables so that you can just read them into your /etc/profile.local script (or wherever you decide to install the variables on your machines).

Lest anyone suggest that the command line is dead and that GUI rules, note that my battery life/charge actually approaches specification at the command line, but only runs about 65% of spec while running X Window.

The Bash-Prompt HOWTO

There is a Bash-Prompt-HOWTO written by Giles Orr that explores the command prompt more fully. He actually colorizes his prompt without using the PROMPT_COMMAND, notes a couple of bugs, provides lots of examples including a color demo, and even goes so far as to discuss xfonts and display his <user>@<host> in the title bar of his XTerms.

The purpose of this article isn't to simplify Giles's HOWTO, but just to share a simple implementation of a neat and useful feature that isn't mentioned in the introductory Linux books.

Kirk has been dabbling with writing and computers for the past several years. He can be reached at kirk@batmansbyte.org.