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.
|