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.
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.
The Shell Curses library consists of the following functions, arranged in a logical usage order:
Table 1: Standard Shell Curses functions
| Function | Arguments | Description | |
|---|---|---|---|
| initscr | Initializes the shell curses screen addressing system | ||
| endwin | De-initializes the curses screen addressing system | ||
| [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. | |
|
${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
| Function | Arguments | Description |
|---|---|---|
| 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. |
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.
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.
Learn
- The AIX Curses Library provides a set of functions that enable you to manipulate a terminal's display regardless of the terminal type.
- The tput command makes terminal-dependent information available to the shell.
-
The developerWorks AIX and UNIX zone hosts hundreds of informative articles and introductory, intermediate, and advanced tutorials.
-
Stay current with developerWorks technical events and webcasts.
-
Technology bookstore Browse this site for books and other technical topics.
Get products and technologies
- Get the latest version
of Shell
Curses.
Discuss
-
Participate in the AIX and UNIX forums:
- AIX 5L -- technical forum
- AIX for Developers Forum
- Cluster Systems Management
- IBM Support Assistant
- Performance Tools -- technical
- Virtualization -- technical
- More AIX and UNIX forums
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.





