Linux Fu: Controlling The Terminal

A Linux terminal has a lot more features than the TeleType of yore. On a TeleType, text spews out and scrolls up and is gone forever. A real terminal can use escape characters to do navigate around and emulate most of what you like about GUIs. However, doing this at the lowest level is a chore and limits portability. Luckily, all the hard work has already been done.

First, there’s a large database of terminal capabilities available for you to use: terminfo.  And in addition, there’s a high-level library called curses or ncurses that simplifies writing programs to control the terminal display. Digging deep into every nook and cranny of ncurses could take years. Instead, I’m going to talk about using a program that comes with ncurses to control the terminal, called tput. Using these two commands, you can figure out what kind of terminal you’re dealing with, and then manipulate it nearly to your heart’s content. Let’s get started!

About terminfo

The most fundamental question you have to ask is what kind of terminal are you talking to. The answer is in the $TERM environment variable and if you ask your shell to print that variable, you’ll see what it thinks you are using: echo $TERM.
For example, a common type is vt100 or linux console. This is usually set by the system and you shouldn’t change it unless you have a good reason. In theory, of course, you could manually process this information but it would be daunting to have to figure out all the way to do something like clear the screen on the many terminals Linux understands.

That’s why there’s a terminfo database. On my system, there are 43 files in /lib/terminfo (not counting the directories) for terminals with names like dumb, cygwin, ansi, sun, and xterm. Looking at the files isn’t very useful since they are in a binary format, but the infocmp program can reverse the compilation process. Here’s part of the result of running infocmp and asking for the definition of a vt100:


vt100|vt100-am|dec vt100 (w/advanced video),

       am, mc5i, msgr, xenl, xon,

        cols#80, it#8, lines#24, vt#3,

        acsc=``aaffggjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,

        bel=^G, blink=\E[5m$<2>, bold=\E[1m$<2>,

        clear=\E[H\E[J$<50>, cr=\r, csr=\E[%i%p1%d;%p2%dr,
 

This might not look much better than the binary version. But you can probably guess that it is telling you, in part, that the bell character (bel) is Control-G (^G). You can also probably guess that \E is an escape character and puzzle out how to make characters bold or clear the screen. Some of the data is structural such as 24 lines and 80 columns of output. The capabilities (like xon for using XON/XOFF protocol) are a bit harder to puzzle out without looking it up.

The exact meanings, though, aren’t important. Because we are going to use higher-level constructs to tell tput what we want and it — or at least the ncurses library — will look up the data in terminfo and then do the right thing.

Using tput for Information

I’m not sure where the name tput came from — you have to assume it is “terminal put.” But you can use tput to get information, too. For example, instead of looking at $TERM, you can say tput longname. You can learn other things, too. For example, how wide is the terminal (in characters)? tput cols.

This is especially handy for terminals under X Windows that might change size. In a script you can trap the WINCH event to run some code when the terminal resizes, which can be very handy. Other things you can read is the number of lines and the number of colors.

For example, assuming you have fortune installed, you might want a little fortune window that updates every 30 seconds. It would be nice, though to have the fortune center even if the screen changes size.

#!/bin/bash
# Simple centered fortune
TIMEOUT=30


do_print() {
  w=$(tput cols)
  h=$(tput lines)
  n=${#text}
  spaces=$(( ($w-$n)/2 ))
  lines=$(( $h/2 ))
  clear
  for I in `seq 1 $lines`
  do
   echo 
  done
  for I in `seq 1 $spaces`
  do
    echo -n ' '
  done
  echo $text
}

while true
do
  text=$(fortune -n $(tput cols) -s )
  do_print
  sleep ${TIMEOUT:-30}
done

This works pretty well. When a fortune appears it will be in the center of the window. I do all the drawing in do_print because I have future plans for that function.

The fortune command tells it to only give us short fortunes and the definition of short is the width of the screen according to tput. I use a few bash-specific techniques here including expression evaluation, string length(${#text}), and default parameters

You’ll notice, though, if you resize the window, the fortune won’t be in the right place until a new one appears. How can we fix that?

Use a WINCH

Turns out, the program should get a SIGWINCH signal when the terminal resizes. I say “should” because there are cases where this doesn’t occur, for example in some emacs shells. (In fact, a lot of this breaks in emacs.) There are probably other setups that could have problems, too. However, assuming the code gets SIGWINCH, it is easy to handle it. This line of code should take care of it: trap do_print WINCH

The only problem is, it doesn’t work right for this script. The script spends most of its time sleeping and it will only get the signal when it wakes up. But by that time, you’ll also get a new fortune, so while it does the right thing, it either does it with the new fortune or it does it with the old fortune and is immediately overwritten.

The answer is to sleep less. However, it is rude to the rest of the system to simply sit and spin, so I change the sleep to this:

# Can't just sleep for timeout
# because we won't get SIGWINCH when sleeping
# If you want faster response, sleep less
TO=${TIMEOUT:-30}
TO=$(( $TO * 2 ))
for I in `seq 1 ${TO}`
do
   sleep 0.5
done

This is a little over complex because I wanted to sleep for 500 milliseconds instead of a full second. The less you sleep the more responsive the script is to changes. However, the more system resources you waste, so it is a tradeoff.

Formatting

Learning the size of the screen is pretty useful. But you can also use tput to format text:

  • blink – Blinking text
  • bold – Bold text
  • invis – Invisible text
  • rev – Reverse video
  • rms0 – End standout mode
  • rmul – End underlined text
  • setab – Background color
  • setaf -Foreground color
  • sgr0 – Turn off all attributes
  • smso – Start standout mode
  • smul – Start underlined mode

The colors take a number ranging from 0 black to 7 white. A value of 9 resets the default color. Try changing the echo $text line to look like this:

tput setab 7   # white background
tput setaf 4   # blue text
tput bold
echo $text
tput sgr0

Unfortunately, you can’t use color names unless you define them yourself. If you don’t like using separate tput calls for each item, you can use the -S option and a here document:

tput -S << tputEOF
  setab  7  # white
  setaf  3  # blue
  bold
tputEOF

You still have to put one item per line, though. I can’t say why standout and underline mode have a specific end code and the rest you just have to use sgr0 to turn everything off.

Erasing and Saving

I used clear to clear the screen but I could have used tput clear. In fact, on some systems clear is an alias to tput and the program is smart enough to know if you call it as clear it should clear the screen and exit.

There are a few commands that let you either clear parts of the screen or save and recall the screen:

  • clear – Clear the whole screen
  • ed – Clear to end of screen
  • el – Clear to end of line
  • el1 – Clear to start of the line
  • rmcup – Restore saved screen
  • smcup – Save screen and clear

You can try tput smcup from the command line if you want to see it work. Just issue the command, do some things, and then issue tput rmcup. If you want to color the whole screen, try this in the fortune script, replacing the clear command:

tput setab 2
tput clear

Then take out the setab command further down.

Cursor Control

The last item is to be able to move the cursor under program control:

  • civis – Set cursor invisible
  • cnorm – Set normal cursor
  • cud1 – Move down one line
  • cup – Set row and column
  • cuu1 – Move cursor up one line
  • home – Move cursor to top left
  • rc – Restore saved cursor position
  • sc – Save cursor position

You could do a better job on the script now, to position the print location to the right place by replacing both for loops in do_print with: tput cup $lines $spaces.

Other Ideas

Using tput you can easily create things like progress bars or display data by pages without relying on external pager programs. Of course, if you need something too fancy, you probably ought to go with a GUI program or a dialog-box library. Of course, there are many of these for Linux: dialog, cdialog, zenity, kdialog, and easybashgui come to mind.

But sometimes you can’t, don’t want to, or just don’t need to get involved with more complex libraries. For simply putting text somewhere up on the screen, it’s hard to beat tput.

17 thoughts on “Linux Fu: Controlling The Terminal

  1. Right at the beginning, you say “On a TeleType, text spews out and scrolls up and is gone forever.” Except of course that it still exists on paper, thereby providing a valuable “audit trail” of previously issued commands and responses. Try one, you’ll like it! ;-)

    1. Well what I mean is gone forever from your script. I have owned a KSR28 and ASR33 although at the moment I don’t have anything with hardcopy. I also got started on RTTY with old 5 level machines. The KSR28 mechanism for preventing key jamming was really smart.

      1. Amen to all of that, worked on 15s, 19s, 35s, 33s, and 43s. I was actually given a letter of commendation by NY Telephone Company for replacing 35ASRs used in central offices with 43 ASRs, that apparently no one knew could be done. We had run out of maintenance parts for the 35s used in the COs and were doing some real meatball repairs on them, cannibalizing others that had been taken out of service. It could take a day or two on the bench to get a replacement up and running, which was not satisfactory. The big problem (in other’s minds) was that the 35s had current loop interfaces, and the 43s were serial (RS-232) and I was the only one in the Teletype organization in the whole state that figured out that there was a simple interface available. The next thing I knew I was running around half the state replacing 35s with 43s, using MY special interface, and I trained a couple of other techs to do Manhattan, Long Island, and the western portion of the state.

    1. Ugh. WordPress for some reason will sometimes — and I can’t figure out the conditions — change the greater than/less than to ampersand gt/lt ; (the HTML entity) and then you get to manually go fix all of them. When I fixed them I flipped them. Fixed now. Even this post was painful to compose for the same problem lol

  2. Thanks for the article, I will definitely use tput a lot more in the future (instead of just hard-coding escape sequences *cough*)

    man 5 terminfo
    will show the tput “commands”

    Your bash-scripts could use some bashifying. Don’t use backticks, don’t use seq. Have a look at “shellcheck”, it will tell you about that stuff.

    In one script, you carefully calculated $TO, only to completely forget about it a line later and use $TIMEOUT again. :)

    1. I’ve tried to break the backtick habit but am only successful about half the time. I know the alternative to seq but years ok ksh is hard to break.

      Originally I had the timeout for 1 second and then went to half second and fat fingered it.

  3. These posts are quite handy. Thanks, Al!

    One thing, though… it’d be nice to know how common/established these techniques are… can I expect to write a shell script with tput (seq, or even sleep, which only recently started allowing fractional seconds) to run on any bash terminal, using the same call syntax and same output formatting, or am I going to have to test for its existence and version number before using it in a script that may be run on anything from OSX 10.4 to OpenWRT to Pibuntu(?), Debian 7, and so-forth?
    I’m drawing a blank at the moment, but some command-line utils I thought were well-established seem to change their arguments with every new version… and then some are different on linux vs. bsd… ‘find’ comes to mind, ‘stat’, and a few others.
    Sometimes I’ve found the version-differences of some tiny utility called only once in a gigantic script to be so-dramatic as to render a reliable script on one machine to be *dangerous* on another… so, I’ve gotten kinda cautious about this sorta thing. (is there some way to look this sorta thing up?)

    Anyhow, keep posting these! Always find ’em useful.

    1. Have a look at the end of the manpage, “PORTABILITY” — to me that reads as a “yes” :)
      (the oldest systems I haved used tput on so far are Solaris 8 and RHEL 3, in case you trust my experience more than a manpage for some reason)

    1. From the bottom of the post: Of course, there are many of these for Linux: dialog, cdialog, zenity, kdialog, and easybashgui come to mind.

      But check out the link for easybashgui.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.