03 January 2011

The power of echo command: Bash console drawing methods and some usefull tput-like functions








Have you ever try to write some fancy output that will knock off your co-workers?
Yes you will say, no problem, but have you used Bash to do that?
Have you ever tried to do some menus and graphics under bash?
Yes you will say again, 'dialog' is the right choice for such a task.
But I am very stubborn and I want pure Bash, no external programs, but still some menus and graphics,
and if possible no external programs at all.
And I want at least so much capabilities to be able to build a simple boxes with some colored text.
Is it possible?
Well if you tried to do you homework, you definitely came to some
death end, because bash manual page is not very explanatory on that issue.
But I want to have some old-style text-mode graphics, menus and perhaps message boxes.
And I want to be able to put text where-ever I want on my console.
You would be surprised if I tell you, that most of that can be done with a simple 'echo'
command which is also Bash-builtin.
How I did it
A few years ago my boss tried to convince me that curses library is the only
possible option to produce something fancy on my terminal. But once I opened RedHat's
/etc/init.d/functions file and extracted option for positioning characters at
the current line but at the custom column. I thought - if this is possible, what
else can be done with this nice echo command?
My next step was to run RedHat Setup program and redirect it to an ordinary text file.
That one took me some time. What I've got was a bunch of unreadable characters
that should be responsible for drawing something to the screen. But how? I was confused
for many months. No documentation available or I just did not know how to search for it.
Then I played around with different options the way I deleted part by part of 'garbage' file
produced with some tui aplication outputs and I found out how to switch between
writing and drawing mode at the command line. That one was a real break on my
path of creating some simple pure Bash menu lines.
At the end I redirected some ncurses programs to an empty file and observed what happened.
I ofcource tried many other combinations that work on some terminals, but below is
my result, that should work on most terminal emulators used today.
So, yes you will also need terminal emulator, that supports codes below.
I tested all that stuff on putty and at the Linux console.
Step by step
Right here before you are some not very self-explanatory,
small and usefull functions.
But do not worry, I will explain everything to you.
I will also show you step by step, how you can make those
functions usefull for fancy bash programming.
The Power of echo command
We will start with a simple echo.
Echo can be as powerfull as you can't even imagine.
Just use it with an 'escape' character and extended
option and it will print for you allmost anything.
This 'echo' commad is also bash internal, builtin.
So things just could not get any better.

Creating Custom ESCAPE character
It is mentioned in many manual pages but I did not understood the meaning
of it at he beginning. With the simple words: to make
some characters behave special way, you should put an ESC character infront
of them. ESC character is a single character that takes two places. So it
is impossible just to enter it over keyboard. This is known 'echo' feature and
makes echo a very powerfull command.
echo -en "\033"

Creating Custom Function CLEAR
For start we need a clean screen to work on.
Replacement for 'tput clear' can be simply performed
just by executing 'clear' command.
But I want to be sure just to use bash internals.
So I created a simple echo command, that produces screen erasing.
Escape character in combination with 'c' character will do the work for us.
echo -en "\033c"
Creating Custom Function CIVIS
To create programs with a more realistic look, sometimes it
is nice to hide cursor character.
Replacement for 'tput civis' is reverse engineered.
I typed tput civis and redirected an output to an ordinary text file.
Then I looked inside using vi editor. And here it is.
It will hide your cursor just as good as a real 'tput civis'.
echo -en "\033[?25l"
Creating Custom Function CNORM
Replacement for 'tput cnorm' was created with the same method as command above.
Its function is to make cursor visiible again.
echo -en "\033[?12l\033[?25h"
Creating Custom Function TPUT
Replacement for 'tput cup' should put your cursor anywhere on your terminal.
This can be also easily performed using syntax: echo -en "\033[10:10H".
But since I want to be able to specify my own x and y coordinates I am using
positional parameters ${1} and ${2} which will save my custom console input
and transform it into something usefull.
echo -en "\033[${1};${2}H"
Custom Function COLPUT
RedHat function for column put. This one was hacked from RedHat functions file.
Function is responsible for start writing at the exact position on the current
line without calculating in which line is cursor currently positioned. RedHat
is using it to display services statuses at startup process.
echo -en "\033[${1}G"
Custom Function MARK
Replacement for 'tput smso' should reverse background color around typed text.
I found it the way I simply tested all numbers from 0 to 50. Some were very
interesting but this was the only one more or less working on most terminal
emulators.
echo -en "\033[7m"
Custom Function UNMARK
Replacement for 'tput rmso' that returns selected text back to
normal is created as easily as previous function.
All you need to change is background color back to default (27m).
The code below will do the trick:
echo -en "\033[27m"
Custom Function DRAW
This one along with function DRAW I discovered trying out a bunch of code, that RedHat's
'setup' creates when output is redirected into a file.
When I was seeking for a decend background coloring piece, some garbage came to my screen.
I did not realise at first what this is good for and I thougt it is some console bug.
But after short test it became clear to me that this garbage is actually simple line
drawing and will be very usefull. To be sure to work also on other linux distributions with
some different terminal settings, some RedHat's black magic '%@' is needed. This
will load default console font with special characters that you will need to draw
lines. If not done so, small case characters could be interpreted wrongly. So now we need
to combine 'font switching' and 'draw' mode as two separated commands. I tried to combine
them into one, and so can you, but sometimes the result is not satisfactory.
echo -en "\033%@";echo -en "\033(0"
Custom Function WRITE
When I found 'drawing' mode I also wanted a simple 'return to writing' mode.
Painfull testing method followed.
I tried all letters and numbers and I succeeded!
A very short code below will switch from so called 'garbaged' console back to normal.
echo -en "\033(B"
Custom Function BLUE
To be short I just named it blue.
But it can be easily turned into some other background color.
Search for string 44m and replace it with some of strings listed below.
Result should be impressive.
If not, your terminal does not support this kind of coloring.
What I did is actually very simple.
I reset screen (\033c), I set white color for font and
blue for background ([37;44m) and at the end
I want to apply all of this to the whole screen not only
to my current row ([J).
Select the following codes: 40m for black, 41m for red,
42m for dark green, 43m for light green, 44m for blue,
45m for pink, 46m for green and 47m for gray.
echo -en "\033c\033[1;44m\033[J"

Putting it all together
Here is an example of how to create a true bash functions.
To be always ready, put those function (or the ones you need)
to the start of your every bash script.
This way you will fasten your fancy Bash coding.
ESC(){ echo -en "\033";}                            # escape character
 CLEAR(){ echo -en "\033c";}                           # the same as 'tput clear'
 CIVIS(){ echo -en "\033[?25l";}                       # the same as 'tput civis'
 CNORM(){ echo -en "\033[?12l\033[?25h";}              # the same as 'tput cnorm'
  TPUT(){ echo -en "\033[${1};${2}H";}                 # the same as 'tput cup'
COLPUT(){ echo -en "\033[${1}G";}                      # put text in the same line as the specified column
  MARK(){ echo -en "\033[7m";}                         # the same as 'tput smso'
UNMARK(){ echo -en "\033[27m";}                        # the same as 'tput rmso'
  DRAW(){ echo -en "\033%@";echo -en "\033(0";}        # switch to 'garbage' mode
 WRITE(){ echo -en "\033(B";}                          # return to normal mode from 'garbage' on the screen
  BLUE(){ echo -en "\033c\033[0;1m\033[37;44m\033[J";} # reset screen, set background to blue and font to white
Some real life examples using functions above
Now you have all those functions but you still do not know
how to use them? Let me start with some nice examples and
show you some nice tricks.
Box with a simple progress indicator






 EXECUTING IN PROGRESS:     /    






  

Example shown above can be used in any program that will
take some time to execute.
Code will perform the following tasks:
  1. clear screen (optional)
  2. paint screen (optional)
  3. hide cursor
  4. enable drawing mode
  5. draw fancy box
  6. put text into box
  7. put progress indicator into box
  8. create mechanizm to be able to stop execution
  9. make some space for real program execution
  10. enable writing mode again
  11. put rotating sign always at the same place on the screen
  12. stop screen activity and return the cursor back to visible mode




If you start your code from putty, you should get result similar to the one above.
You will be able to observe rotating cursor for about five seconds. As you can see
there is not too much of code needed (only 42 lines). Give special attention to the
line with a 'trap' command. This one is needed to prevent our script getting
out of control. When you press ctrl+c or some other interrupt combination, 'trap'
will also delete controlling file '/tmp/.waiting' and force subscript to stop.

As you can see a lot of 'q' and some other characters are used. Those characters
will draw some lines inside "drawing" mode for you. To see other characters, switch
to drawing mode and try them out:


Only small case characters will draw something for you.
But if you press large case character it will just present itself as a normal character.
Since screen that looks like a garbage can cause a little panic with you. To be sure, that
you have everything under control, just type 'reset' and it will normalize screen for you again.




Characters shown above should be supported by most terminal emulators. But on some terminals
you will also be able to to get more "drawing" characters to your screen. This way you will also
make your program more terminal dependable. So just try to stick with characters shown above.

Next two articles:
Some fancy and usefull screensavers for system administrators.

Creating fancy pure Bash selection tui-mode menu methods.




echo -en "\033%@";echo -en "\033(0"

Q ─   W ┬   T ├   Z ≥ 

U ┤   A ▒   F °   G ±

J ┘   K ┐   L ┌   Y ≤

X │   V ┴   N ┼   M └

#!/bin/bash
 CLEAR(){ echo -en "\033c";}                            # Predefined functions
 CIVIS(){ echo -en "\033[?25l";}                        # (see description above)
 CNORM(){ echo -en "\033[?12l\033[?25h";}               #
  TPUT(){ echo -en "\033[${1};${2}H";}                  #
  DRAW(){ echo -en "\033%@";echo -en "\033(0";}         #
 WRITE(){ echo -en "\033(B";}                           #
  BLUE(){ echo -en "\033c\033[0;1m\033[37;44m\033[J";}  #

   touch /tmp/.waiting                                  # create an empty control file
   trap "rm -rf /tmp/.waiting;exit 2" 1 2 3 15          # catch interrupt keys (like ctrl+c)

   P1(){ echo -n  '|';}                                 # function P1 (for progress)
   P2(){ echo -n  '/';}                                 #          P2
   P3(){ echo -n '-';}                                  #          P3
   P4(){ echo  -n '\';}                                 #          P4

   CLEAR                                                # clear screen
   BLUE                                                 # make background blue and font white
   CIVIS                                                # hide cursor

   echo -e ""                                           # empty line
   echo -e ""                                           # empty line again

   DRAW                                                 # switch to drawing mode
   echo -e "  lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk"         # draw box lines
   echo -e "  x                              x"         #
   echo -e "  x EXECUTING IN PROGRES:        x"         # use large case, since small ones
   echo -e "  x                              x"         # are now used used for drawings
   echo -e "  mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj"         #
   WRITE                                                # switch back to normal text input mode

   i=0                                                  # set variable 'i'
   while [ -f /tmp/.waiting ]                           # while file exist
   do                                                   # 
    i=$((++$i))                                         # increase variable i by one each time
     TPUT 5 30 ;P$i                                     # put one of P1, P2, P3 or P4 to exact position

     if [ "$i" = "4" ]; then                            # make sure that variable 'i' will never
        unset i                                         # increase over 4
     fi                                                 #

    usleep 100000                                       # 0.1 second sleep (microsleep)
   done &                                               # fork a subprocess and execute it in background

   # some operation here                                # customize for your needs (put some custom commands)

   sleep 5                                              # sleep for another 5 seconds
   rm -rf /tmp/.waiting                                 # remove control file and stop execution

   TPUT 10 10                                           # put cursor out of the box to another place
   echo ""                                              # make an empty line
   CNORM                                                # Normalize cursor view

5 comments:

  1. pretty good. nice to learn these tricks.

    ReplyDelete
  2. Hi,
    I like to play in bash but I never saw anything like this...
    I've searched in the man bash for some tricks like this but as you mentioned in the article bash's man is not very explanatory.
    I'm happy that i found your blog, it's very helpful for me.
    Thanks, you made my day :D


    ReplyDelete
  3. Hi I Used you example in order to make a menu for some coworkers in order to they can create users, groups, samba users, passwords options etc, if you wish i can show you, thanks a lot for your work

    ReplyDelete
  4. Gee! You have no idea how long i was looking for the right esc-sequences to make my programm drawing on the console (not xterm, the real one). Just by adding your DRAW and WRITE functions to it, without checking for pts or tty makes it working on both. Thanks for these incredible findings and sharing it. Even though i had a hard time to find this page here. I will keep your stuff in a safe place. Thank you
    Also, if you are interested i will share that script with you. It's simply ment to list mail accounts and start them with mutt.

    ReplyDelete