Tinkering in the terminal with tput

Posted by Nate Bargmann on Sun, Feb 2, 2020

While it looks like a throwback to the 1980s or even the late 1970s, the terminal is a very capable way of performing tasks on a POSIX (BSD, GNU/Linux, etc.) computer.  Some tasks are simply better suited to a Text User Interface (TUI) than a Graphical User Interface (GUI) that runs under X or Wayland.  Even better, a terminal application is included as part of the various GUI desktops or, for the minimalist, stand-alone terminals such as XTerm or URxvt are available.  On hardware too limited to run a GUI well, the console is always available and TUI and Command Line Interface (CLI) programs are the rule.  I will differentiate between a TUI program that often runs in full screen mode such as top or Midnight Commander or a CLI program that requires various arguments to control its behavior such as ls or cp.  Additionally, TUI programs often display a limited set of psuedo-graphics character such as lines, tees, and corners to draw boxes and such on the screen.

Modern terminals and the console do support various capabilities that provide a visual enhancement to the characters displayed on the screen including multi-language character sets such as UTF-8 (not to be confused with the amateur radio digital mode FT8), color, bold, underline, and italics.  All of these good things do come at a price (don’t they always?) and that price is backward compatibility.  That is where terminfo and tput enter our vocabulary.

Terminals have a long history in POSIX/Unix from the earliest days as a more-or-less specialized printer to a terminal featuring a glass teletype display and keyboard to today where terminals are a software emulation presented on our monitor’s screen.  All of this baggage history carries forward to the present day.  In some ways the backward compatibility is a blessing and a curse!  Which leads us to curses, a programming library that gives programs access to terminal control functions through a uniform programming interface.  The current project is ncurses or “new curses” and is the version found on almost all POSIX systems today.  The ncurses project is the home of the terminfo library and tput.

With that bit of history out of the way, tput and its use in ~/.bashrc will be the focus of this article.

Escape sequences

The first way of adding color to the shell prompt or to manual pages is often learned by way of ANSI escape sequences.  Escape sequences are fun to play with and much can be learned about the basics of terminal behavior through their use.  The following screenshot shows the sequences to print text in color:

Screenshot showing ANSI escape sequences for red, white, and blue text in the terminal.

ANSI escape sequences

In this case I added the bold escape code to distinguish the word “White” from the normal darker white (gray) of “and” (click on the image for a larger view).  Many resources for learning about these codes and their use can be found on the Web and a good one is FLOZz’ MISC.

While simple to implement, the sequences do have a drawback that advanced effects that work with a given terminal emulator may not work with the next.  Besides not working as expected the unrecognized sequence will likely just be printed as normal text or some special effect code will cause a different effect entirely.  In short, the ANSI escape sequences are not portable.  If you always only use a given terminal emulator this isn’t much of a problem  Wanting to use an effect like underlining or italics may work well with your desktop’s terminal application but will fail on the Linux console, for example.

Enter tput

The tput utility gives us a portable way of querying the terminfo database to determine what escape sequence, if any, exists for a desired character attribute.  For example, Xterm and terminals that set the TERM environment variable to xterm support printing text with the italics attribute.  The Linux console, however, does not support italics.

Consider the following.  In my ~/.bashrc I have the following line which assigns the escape sequence for setting the italics attribute to a shell variable:

ITA=$(tput sitm)

In an Xterm terminal the variable is assigned the following escape sequence:

$ set | grep 'ITA='
ITA=$'\E[3m'

While in the Linux console the variable is not assigned any value:

$ set | grep 'ITA='
ITA=

With hardcoded escape sequences in ~/.bashrc at best the sequence would be ignored by the Linux console and at worst the sequence characters would be displayed or some interesting but uninteded effect might happen.  In other words, the behavior is undefined.

Here we see green text instead of the expected italics on the Linux console when a raw escape sequence is used:

Screen capture of a Linux console showing that an ANSI escape sequence for italics displays green text instead.

Green text instead of italics on the Linux console.

With the use of tput my ~/.bashrc is now portable to various Xterm type terminals and the Linux console.  As the variable has no assigned value on the Linux console, wherever it is used an empty string (in other words nothing) will be inserted in its place.

tput in ~/.bashrc

The following assignments are in my ~/.bashrc:

# Use variables set from terminfo capabilities to make PS1 and LESS_TERMCAP_*
# variables less cryptic.
#
# Colors: 0, Black; 1, Red; 2, Green; 3, Yellow; 4, Blue; 5, Magenta; 6, Cyan; 7, White.
# Foreground (text) colors
BLK=$(tput setaf 0)
RED=$(tput setaf 1)
GRN=$(tput setaf 2)
YEL=$(tput setaf 3)
BLU=$(tput setaf 4)
MAG=$(tput setaf 5)
CYA=$(tput setaf 6)
WHT=$(tput setaf 7)

# Background colors
BLKB=$(tput setab 0)
REDB=$(tput setab 1)
GRNB=$(tput setab 2)
YELB=$(tput setab 3)
BLUB=$(tput setab 4)
MAGB=$(tput setab 5)
CYAB=$(tput setab 6)
WHTB=$(tput setab 7)

# Character attibutes
BLD=$(tput bold)
ITA=$(tput sitm)
NOR=$(tput sgr 0)

and my PS1 is set as:

PS1='(\[$CYA\]$SHLVL\[$NOR\])[\[$YEL\]\j\[$NOR\]]\[$GRN\]\u\[$NOR\]@\[$BLD$GRN\]\h\[$NOR\]:\[$BLD$BLU\]\w $(__git_ps1 "\[$NOR\](\[$MAG\]\[$ITA\]%s\[$NOR\])")\n\[$BLD$WHT\]\$\[$NOR\] '

Note: The old blog broke the string assigned to PS1 across multiple lines, the current blog should have it all on one line.  In ~/.bashrc it should all be on one line.

PS1 is the primary prompt as seen in the screenshot above.  As a bonus some text is appended (not active in the home directory) at the end of the path that shows what branch I am working in whenever I am in a Git repository’s working tree as shown in the screenshot below:

Screen capture of a GNOME Terminal session showing the colored prompt in a directory with a Git tree showing its branch and other status.

GNOME Terminal with the prompt showing Git status

Some other notes about my prompt.  The first value in parentheses is the shell level.  This value will increment if a prompt is opened in a sub-shell.  The next value in brackets is the number of jobs that are running in this shell.  Remember that processes can be put into the background, often with the Ctrl-Z key combination.  Next is my username @ hostname.  I use this prompt on other machines and with virtual machines with various colors so this helps keep things straight.  Then the path and finally the Git branch if in a repository working tree.

Unifying the colors of manual pages

Without some modification the colors and styles of text used in displaying manual pages through the less utility will differ between an Xterm and the Linux console.  To make them appear as close as possible I have the following variables set in my ~/.bashrc:

# Use terminfo capabilities to styleize man pages.
#
# See:
# https://unix.stackexchange.com/questions/119/colors-in-man-pages
# https://unix.stackexchange.com/questions/108699/documentation-on-less-termcap-variables
export LESS_TERMCAP_md=$BLD$WHT             # enter double-bright mode - bold white
export LESS_TERMCAP_me=$NOR                 # leave double-bright, reverse, dim modes
export LESS_TERMCAP_so=$BLD$CYA$BLUB        # enter standout mode - bold cyan on blue background
export LESS_TERMCAP_se=$(tput rmso)$NOR     # leave standout mode
export LESS_TERMCAP_us=$ITA$CYA             # enter underline mode - italics, cyan
export LESS_TERMCAP_ue=$(tput ritm)$NOR     # leave underline mode
export LESS_TERMCAP_mr=$(tput rev)          # enter reverse mode
export LESS_TERMCAP_mh=$(tput dim)          # enter half-bright mode
export LESS_TERMCAP_ZN=$(tput ssubm)        # enter subscript mode
export LESS_TERMCAP_ZV=$(tput rsubm)        # leave subscript mode
export LESS_TERMCAP_ZO=$(tput ssupm)        # enter superscript mode
export LESS_TERMCAP_ZW=$(tput rsupm)        # leave superscript mode

This is a mixture of previously assigned variables and one-time calls to tput with a given attribute.  In addition I have the following man specific variables set to control the width of the displayed page and some additional settings for less:

# Set maximum width of man pages to 80 characters
export MANWIDTH=80
export MANPAGER='less -s -M +Gg'

The following screenshots show the same manual page in an Xterm and on the Linux console:

Screen capture of a custom color scheme for manual pages in a GNOME Terminal session.

A manual page showing the color settings in a GNOME Terminal

Screen capture of a custom color scheme for manual pages in a Linux console session.

A manual page showing the color settings in a Linux console

As can be seen, the Xterm shows the cyan text in italics while the Linux console does not.  Still, this goes a long way toward having the most consistent color styling for manual pages across the various terminal types.

What else?

Various Web searches for tput will turn up many ways it can be used in shell scripts to do many affects with text and cursor control.  This is just a small example of the capability of this utility.  Explore and have fun!