Shell Curses function library

Terminal functions for shell scripting

"Shell Curses" is a library of script functions that provide the shell programmer the ability to perform text-based cursor movements to specified locations on the screen. This ability permits the creation of menuing and data-entry systems using shell scripts without the need for compiled binaries. These functions are similar to the "C" language "Curses" library.

Dana French, President, Mt Xia Inc.

Mr. French's career in the IT industry has spanned three decades and numerous industries. His work focuses primarily on the fields of business continuity, disaster recovery, and high availability, and he has designed and written numerous software packages to automate the processes of disaster recovery and high availability. He is most noted for his approach to system administration as an automated, business oriented process, rather than as a system oriented, interactive process. He is also a noted authority on the subject of Korn Shell programming.



25 March 2008

Also available in Chinese

Introduction

Shell Curses was created in 1993 to solve the specific problem of needing a portable set of cursor manipulation functions that did not have to be recompiled for every new platform. These functions were originally written in Bourne shell, and have since been migrated to Korn Shell 93, although the current set of functions will work equally well in Bash. Over two million downloads of Shell Curses has occurred since 1993 and it is currently in use by organizations all over the world in a variety of applications. As the author of Shell Curses, I can tell you that the current versions are freely available and can be used without license for any purpose.


Functions

At the time this set of functions was written, 15 years ago, most UNIX® system administrators were also C language programmers and were familiar with the C language function library called Curses, which provides cursor manipulation and text windowing functions. Today, many UNIX system administrators are not as familiar with cursor manipulation and text windowing because of the proliferation of graphical interfaces. Although the need still exists for these functions, the knowledge base appears to be shrinking. The purpose of this article is to raise the awareness of the continuing need and the existence of the Shell Curses function library.

The difference between the C language Curses and the Shell Curses function libraries is the C language library is a compiled binary and must be compiled and linked into other compiled programs. The Shell Curses function library is a pure shell script implementation of many Curses functions, and does not require compilation. The only UNIX utility used by the Korn Shell 93 version of Shell Curses is “tput”. See the Resources section for more information on tput, as it will not be discussed in this article. The Shell Curses functions have the same names and argument structures as the compiled Curses functions, so the programmers who are familiar with Curses will also be familiar with how to use Shell Curses, although the Bourne or Korn shell script syntax will be different from the C language.

Shell Curses library

The Shell Curses library consists of the following functions, arranged in a logical usage order:

Table 1: Standard Shell Curses functions
FunctionArgumentsDescription
initscr Initializes the shell curses screen addressing system
endwin De-initializes the curses screen addressing system
refresh
[BufferName] Empties the logical buffer to the screen.

If you specify an argument string to this function, it will echo the cursor commands stored in the environment variable BufferName to the screen.

clear Clears the screen.
move ${RowNbr }
${ColNbr}
Moves the logical cursor to the row and column specified. Requires row and column number arguments.
mvcur
${RowNbr}
> ${ColNbr}
Move the physical cursor to the row and column specified. Requires row and column number arguments.
addch X Prints the character on the screen at the current position. Requires a single character argument.
addstr ${String} Prints the value of String on the screen at the current position.
mvaddch ${RowNbr}
> ${ColNbr} X
Moves the logical cursor to the row and column specified. Prints the specified character on the screen at the row and column specified. Requires row and column number arguments.
mvaddstr ${RowNbr}
${ColNbr}
${String}
Moves the logical cursor to the row and column specified. Prints the value of String on the screen at the row and column specified. Requires row and column number arguments, also requires a String argument.
clrtoeol Clear the current line from the current column to the end of the line.
clrtobot Clear the screen from the current column to the end of the screen.
getch Retrieve one character from standard input.
getstr Retrieve a string of characters from standard input.
insch Insert a character at the current screen position.
mvinsch ${RowNbr}
> ${ColNbr}
Moves the logical cursor to the row and column specified. Insert a character at RowNbr, ColNbr

Requires row and column number arguments.

insertln Insert one line at the current screen position.
delch Delete one character at the current screen position.
mvdelch ${RowNbr}
${ColNbr}
Moves the logical cursor to the row and column specified. Delete one character at the current position. Requires row and column number arguments.
deleteln Delete the line at the current row.
attroff Turn off all screen attributes.
attron Does nothing. Only exists for Curses compatibility.
attrset ${Attribute} Sets the screen attribute defined by Attribute.

Requires an attribute string definition.


Valid Attributes:
rev reverse video
blink Blinking mode
bold Bold Video
dim Half Bright video
smul Start Underscore Mode
rmul End Underscore Mode
sgr0 Exit all Attributes

Enhanced Shell Curses capabilities

The following functions are included in the Shell Curses library of functions and provide enhanced capabilities over and above the regular “Curses” functions:

Table 2: Enhanced Shell Curses functions
FunctionArgumentsDescription
savescr [BufferName] Saves the logical screen buffer into an environment variable defined by BufferName.

Requires a screen name string.

mvclrtoeol ${RowNbr}
${ColNbr}
Moves the logical cursor to the row and column specified. Requires row and column number arguments.
clrtobol Clear the line from the current column to the beginning of the line.
mvclrtobol ${RowNbr}
${ColNbr}
Moves the logical cursor to the row and column specified. Clear the line from the current column to the beginning of the line. Requires row and column number arguments.
mvclrtobot ${RowNbr}
${ColNbr}
Moves the logical cursor to the row and column specified. Clear the display from the current column to the end of the display. Requires row and column number arguments.
getwd Retrieve one word from standard input.
beep Ring the display bell.
chkparm ${String} Determine if value of "String" is null, if so return false, if not return true. Requires a single alpha-numeric argument.
chkint ${Nbr} Determine if value of “Nbr” is numeric, if so return true, if not return false. Requires a single numeric argument.
chklines ${Nbr} Determine if value of “Nbr” is less than or equal to the number of lines on the screen. If so return true, if not return false. Requires a single numeric argument.
chkcols ${Nbr} Determine if value of Nbr is less than or equal to the number of lines on the display. If so return true, if not return false. Requires a single numeric argument.

Examples

The actual code for these functions is not provided here due to length limitations. See the Resources section for download information.

Numerous toolsets have been created using Shell Curses to provide front-end interfaces for installation systems, menuing programs, data entry applications, databases, and more. Some examples showing the use of the various functions will be provided here.

When writing a shell script, one of the first decisions that must be made is whether to use a function library, or to include a file as a “dot” script that contains all required functions. Shell Curses can be utilized by either mechanism and it is left up to the shell programmer to make that decision. The following examples will represent the usage of a Shell Curses as function library, because this the most efficient mechanism for this discussion. As a function library, each function is assumed to exist in a directory as a separate file named the same as the function name. In these examples, the directory used for the Shell Curses function library is /usr/local/function/shellcurses. They also use Korn Shell 93 as the script interpreter as defined on the shebang line (#!/usr/bin/ksh93) at the beginning of each script.

One problem with using the Shell Curses function library under IBM® AIX® is that it already provides compiled binaries named clear and refresh, which will be confused with the shell functions by the same names. To resolve this issue, the clear, refresh, and initscr functions should all be copied into a single file in the function library called initscr. This ensures that when the initscr function is called, the Shell Curses functions for clear and refresh are initialized at the same time, and thus avoids the confusion with the AIX binary commands clear and refresh.

This example uses Shell Curses functions to clear the screen; move the cursor to row 10, column 25; and print the words Hello World! at that location. The cursor is then moved to row 23, column 1. Notice the call to the refresh function. This is the function that actually sends the commands to the screen to be displayed.

	#!/usr/bin/ksh93
	FPATH="/usr/local/functions/shellcurses"
	
	initscr
	clear
	move 10 25
	addstr "Example 1: Hello World!"
	move 23 1
	refresh
	endwin
	
	exit 0

The next example is the same as the previous, except that it uses the single mvaddstr function instead of the two functions, move and then addstr:

	#!/usr/bin/ksh93
	FPATH="/usr/local/functions/shellcurses"
	
	initscr
	clear
	mvaddstr 10 25 "Example 2: Hello World!"
	move 23 1
	refresh
	endwin
	
	exit 0

The third example prompts the user to enter some data, then displays that data at a location lower on the screen. Notice the prompt is contained in a shell variable, and the number of characters in the prompt is used to determine the location on the screen to place the curses for the user to enter their data. Also notice the refresh function is called before the getstr function for the purpose of flushing the commands to the screen before prompting the user for input:

	#!/usr/bin/ksh93
	FPATH="/usr/local/functions/shellcurses"
	
	initscr
	clear
	
	PROMPT="Example 3: Enter some data...:"
	LEN="${#PROMPT}"
	(( COL = LEN + 2 + 1 ))
	
	mvaddstr 10 2 "${PROMPT}"
	move 10 ${COL}
	refresh
	
	ANS=$( getstr )
	mvaddstr 15 2 "Here is the data you entered.: ${ANS}"
	move 23 1
	refresh
	
	endwin
	
	exit 0

The fourth example provides a complete menuing system with a header and error checking for invalid selections. It also redraws the screen after a selection is made and displays the user's selection.

	#!/usr/bin/ksh93
	FPATH="/usr/local/functions/shellcurses"
	SCRWID="80"
	
	initscr
	clear
	
	#### Display a screen header, centered on the screen
	HEADER="Shell Curses Example 4"
	HDRWID="${#HEADER}"
	(( HDRBEGIN = ( SCRWID - HDRWID ) / 2 ))
	mvaddstr 1 ${HDRBEGIN} "${HEADER}"
	
	MENU[0]="First Menu Line"
	MENU[1]="Second Menu Line"
	MENU[2]="Third Menu Line"
	MENU[3]="Fourth Menu Line"
	MENU[4]="Fifth Menu Line"
	ITEMCNT="${#MENU[@]}"
	MENUWID="${#MENU[0]}"
	
	#### Determine the maximum length of the longest menu item
	for MENULINE in "${MENU[@]}"
	do
	  (( ${#MENULINE} > MENUWID )) && (( MENUWID = ${#MENULINE} + 4 ))
	done
	
	#### Display the numbered menu item, centered on the screen
	(( COL = ( SCRWID - MENUWID ) / 2 ))
	NBR="0"
	for MENULINE in "${MENU[@]}"
	do
	  mvaddstr $(( ++NBR + 4 )) ${COL} "${NBR} = ${MENULINE}"
	done
	
	#### Prompt the user for a selection
	mvaddstr 22 2 "Enter the number of your selection: "
	mvaddstr 23 2 "( 0 = Exit )"
	
	#### Read the users selection from the screen
	ANSWER="99"
	while [[ "_${ANSWER}" != _[0-9] ]] ||
	      (( ANSWER < 1 )) ||
	      (( ANSWER > ITEMCNT ))
	do
	  [[ "_${ANSWER}" == _0 ]] && exit 0
	  mvclrtoeol 22 40
	  refresh
	  ANSWER="$( getstr )"
	done
	
	#### Display the item number selected by the user
	clear
	mvaddstr 1 ${HDRBEGIN} "${HEADER}"
	mvaddstr 4 2 "You selected menu item number ${ANSWER}"
	move 23 1
	refresh
	
	endwin
	
	exit ${ANSWER}

The fifth example provides a complete multi-line data-entry system with error checking. This example makes use of a dynamically determined screen width and height, it also uses the reverse video screen attribute to emphasize error messages displayed on the screen.

	#!/usr/bin/ksh93
	FPATH="/usr/local/functions/shellcurses"
	
	initscr
	SCRWID="${MAX_COLS}"
	SCRLEN="${MAX_LINES}"
	clear
	
	#### Display a two line header, centered on the screen
	HEADER1="Shell Curses Example 5"
	HEADER2="Data Entry Screen"
	mvaddstr 1 $(( ( SCRWID - ${#HEADER1} ) / 2 )) "${HEADER1}"
	mvaddstr 2 $(( ( SCRWID - ${#HEADER2} ) / 2 )) "${HEADER2}"
	
	#### Define the user entry prompts
	DOTS="........................................"
	PRMPT[1]="Enter an existing directory name"
	PRMPT[2]="Enter an existing regular file name"
	PRMPT[3]="Enter an alpha-numeric value"
	PRMPT[4]="Enter a numeric value"
	PRMPT[5]="Enter an executable file name"
	PCNT="${#PRMPT[@]}"
	
	#### Display the numbered data entry prompt with trailing dots
	for IDX in "${!PRMPT[@]}"
	do
	    (( DOTWID = ( SCRWID / 2 ) - ( ${#PRMPT[IDX]} + 4 ) ))
	    PRMPT[IDX]="${PRMPT[IDX]}${DOTS:0:${DOTWID}}"
	    mvaddstr $(( IDX + 4 )) 2 "${IDX}: ${PRMPT[IDX]}...:"
	done
	DATACOL="$(( ${#PRMPT[1]} + 10 ))"
	
	#### Prompt the user for a selection
	mvaddstr $(( SCRLEN - 3 )) 2 "Select a line number to enter data:"
	
	#### Read the users data line number selection from the screen
	while :
	do
	#### Read the line number entered by the user
	    mvclrtoeol $(( SCRLEN - 3 )) 40
	    refresh
	    ANS="$( getstr )"
	
	#### clear the status line and re-display the default status message
	    mvclrtoeol $(( SCRLEN - 2 )) 2
	    mvaddstr $(( SCRLEN - 2 )) 2 "( 0 = Exit )"
	
	#### Validate the user entered line number, if invalid go back and ask again
	    [[ "_${ANS}" == "_0" ]] && exit 0
	    if [[ "_${ANS}" != _+([[:digit:]]) ]] || (( ANS < 1 )) || (( ANS > PCNT ))
	    then
	        continue
	    fi
	
	#### Read the line data from the user entry
	    mvclrtoeol $(( ANS + 4 )) ${DATACOL}
	    refresh
	    DATA[${ANS}]="$( getstr )"
	
	    mvclrtoeol $(( SCRLEN - 2 )) 2
	    attrset rev
	
	#### Check the validity of the line 1 data as entered by the user
	    if (( ANS == 1 )) && [[ ! -d "${DATA[${ANS}]}" ]]
	    then
	        mvclrtoeol $(( ANS + 4 )) ${DATACOL}
	        mvaddstr $(( SCRLEN - 2 )) 2 "ERROR: Invalid Directory Name"
	    fi
	
	#### Check the validity of the line 2 data as entered by the user
	    if (( ANS == 2 )) && [[ ! -f "${DATA[${ANS}]}" ]]
	    then
	        mvclrtoeol $(( ANS + 4 )) ${DATACOL}
	        mvaddstr $(( SCRLEN - 2 )) 2 "ERROR: Invalid Regular File Name"
	    fi
	
	#### Check the validity of the line 3 data as entered by the user
	    if (( ANS == 3 )) && [[ "_${DATA[${ANS}]}" != _+([[:alnum:]]) ]]
	    then
	        mvclrtoeol $(( ANS + 4 )) ${DATACOL}
	        mvaddstr $(( SCRLEN - 2 )) 2 "ERROR: Invalid Alpha-Numeric Value"
	    fi
	
	#### Check the validity of the line 4 data as entered by the user
	    if (( ANS == 4 )) && [[ "_${DATA[${ANS}]}" != _+([[:digit:]]) ]]
	    then
	        mvclrtoeol $(( ANS + 4 )) ${DATACOL}
	        mvaddstr $(( SCRLEN - 2 )) 2 "ERROR: Invalid Numeric Value"
	    fi
	
	#### Check the validity of the line 5 data as entered by the user
	    if (( ANS == 5 )) && [[ ! -x "${DATA[${ANS}]}" ]]
	    then
	        mvclrtoeol $(( ANS + 4 )) ${DATACOL}
	        mvaddstr $(( SCRLEN - 2 )) 2 "ERROR: File is not executable or 
             does not exist"
	    fi
	    attroff
	    refresh
	
	done
	
	endwin
	
	exit ${ANS}

These examples represent a small part of the applications and usage of the Shell Curses function library. A standardized menuing and data entry system based on Shell Curses exists under French Menus.

This menuing system provides the shell programmer with the ability to create multi-level menuing systems and multi-screen data-entry programs that can interface with a wide variety of programs and databases. If no back-end program or database is specified, it uses a built-in database system to store the user-specified information. A demonstration program is provided to illustrate the capabilities. French Menus also utilizes most, if not all, of the functions in the Shell Curses library and is a good reference tool to use when designing and implementing other applications.


Conclusion

The purpose of this article is to emphasize the continuing need for text-based applications and programs, and to raise the awareness of existing tools created for this purpose. Function libraries such as Shell Curses and French Menus make the creation of text-based applications easier, and provide a standard by which shell programming can be measured and evaluated. Since many UNIX programmers are aware of Shell Curses, or at least Curses, writing your shell programs based on this set of functions will make them supportable, extensible, and maintainable.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into AIX and Unix on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=AIX and UNIX
ArticleID=296899
ArticleTitle=Shell Curses function library
publish-date=03252008