Returning a Single Character in a UNIX Shell Script
How many times when programming a UNIX shell script have you wanted to echo "Press any character to continue" without having the user press a carriage return? Or in a shell menu, have you ever wanted to have the user choose an option without the character being echoed? The UNIX shell doesn't provide an individual command for returning a non-echoed single character, but manipulating the terminal driver produces the same effect.
The terminal driver controls input between the shell script and the UNIX kernel. The driver default is canonical or line-based mode; that is, entered characters are buffered and not passed to the kernel until the carriage return is pressed.
To process just one character, turn off buffering by putting the terminal in raw or single-character mode. This article presents two methods for controlling the terminal mode; one method is a C program, and the other is a function written in shell.
Whichever method is used, the steps necessary to set raw mode, get the character, and set back to canonical are:
1. Save original terminal settings.
2. Turn off canonical mode and echoing.
3. Get the character.
4. Reset the original terminal settings.
5. Return the character.
The ret_char C Program
The crux of the C program, shown in Listing 1, is controlling the terminal driver with the I/O control command function, ioctl(). The three arguments for this function are:
1. File designator - 0 for input.
2. Request - TCGETA for getting current terminal settings or TCSETA for getting the settings.
3. termio structure - This structure contains all the information for the terminal device. The full structure definition is in header file termio.h.
After a call to the ioctl function to save the terminal settings in a termio structure, the following line sets the terminal to "raw" mode and turns off echoing:
chgit.c_lflag &= ~(ICANON|ECHO);
The following two lines instruct the driver to accept one character and to wait zero time units between characters:
chgit.c_cc[VMIN] = 1;
chgit.c_cc[VTIME] = 0;
After a call to the ioctl function to set the new termio structure, the while loop terminates after a valid character is entered; a valid character is a carriage return, an alphanumeric, an interrupt, or a control-c. (See sidebar "Recovering the Terminal" for more information.)
Finally, another call to the ioctl function sets the terminal driver back to the original settings; after returning the character to the main function, printing the character returns it to the shell.
Implement ret_char in a shell script as such:
echo "achar is: "$achar
The Shell Function
The shell solution for returning one non-echoed character is bundling together the UNIX stty and dd commands:
stty -icanon -echo min 1 time 0
dd bs=1 count=1 <&0 2>/dev/null
The stty command, set the options for a terminal, is analogous to the C ioctl function. The -g option displays the terminal settings in a form that can be used by another stty command, typically a colon-delimited string of hexadecimal values. Here, you can save the contents of that string as the value for the oldstty variable and re-establish that state after the single character is returned.
Use the stty command to set the terminal mode (-icanon) and to turn off echoing (-echo). The "min 1" and "time 0" options instruct the terminal driver to accept one character and to wait zero time units between characters, respectively.
The dd command, convert and copy a file, typically is used to input a file or device, execute some conversion process, and send the converted data to another file or device.
In the readkey function, the dd command obtains one character because the block size, bs, is set to 1, and only one character (count=1) is copied from standard input. The number of characters processed by dd is written to standard error. Because this information is extraneous, standard error is redirected to /dev/null.
The following is an example of calling readkey:
echo "Enter a keystrokec" key=`readkey`
echo "\nkey is "$key
In shell, a function must be in back quotes if it returns parameter(s).
I have presented a C program and a shell function for controlling the terminal driver in order to return a single, non-echoing character. Unfortunately, both examples are System V dependent. If you are running Berkeley or some other UNIX variant, look for the ioctl function call and the stty and dd commands to behave differently.
Sage, Russell G. Tricks of the Unix Masters. Carmel, IN: Howard W. Sams & Company, 1987.
About the Author
Ed Schaefer is a frequent contributor to Sys Admin. He is a Senior Software Consultant employed by Meridian Technology Group of Portland, Oregon. Meridian provides Informix, Oracle, Sybase, and SQL Server consultants in the Pacific Northwest and in Arizona. Ed currently consults for a Fortune 500 company in Portland.