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
.
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! ;-)
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.
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.
Nice article. But I think you meant
tput -S <> tputEOF.
Ack! My angle brackets messed up my posting. My point is that the double angle brackets in the article are backwards. It should be two less than signs.
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
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. :)
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.
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.
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)
Cool, thanks!
Pretty sure the “Portability” section doesn’t exist in all manpages for all utilites, but you can bet I’ll be checking for it from now on!
Have you looked at the dialog utility? It can be used to create curses console GUI from a script.
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.
Look at `TO=3 ; ((TO=TO*2)) ; echo $TO` and save some “$”s from now on.
So thats how I can set my terminal to green text on black screen like a real hacker!
Real hackers don’t waste their time on user interface settings. Don’t get too attached to installations that get wiped all the time.
“I’d give my right arm to be ambidextrous” is pure gold!