Input/Output Commands





Input/Output Commands

The Korn shell provides a number of input/output commands, which are covered in the following sections.

The print Command

You've already seen this command a hundred times since it was introduced in Chapter 3, but here it is again. This is a more formal definition of the command that describes the other things it can be used for. The print command displays arguments according to options with this format:

print [options] arguments 

Without options, special characters, or quotes, each argument is displayed separated with a space, and all the arguments are terminated with a newline:

$ print X          Y          Z 
X Y Z 

Notice that all the extra whitespace between the arguments was truncated. We could keep the whitespace by enclosing the arguments in quotes like this:

$ print "X           Y          Z" 
X             Y            Z 
Escape Characters

There are a number of special escape characters that allow you to format the print arguments. For example, to display arguments on separate lines, instead of using multiple print commands:

$ print X; print Y; print Z 
X 
Y 
Z 

the arguments could be separated with the newline escape character \n:

$ print "X\nY\nZ" 
X 
Y 
Z 

Figure print Escape Characters

\a

bell character

\b

backspace

\c

line without ending newline (remaining arguments ignored)

\f

formfeed

\n

newline

\r

return

\t

tab

\v

vertical tab

\\

backslash

\0x

8-bit character whose ASCII code is the 1-, 2-, or 3-digit octal number x

The print arguments could be double-spaced like this:

$ print "X\n\nY\n\nZ" 
X 

Y 

Z 

Make sure escape characters are enclosed in quotes. Otherwise they are not interpreted correctly. Here, without the quotes, \n is interpreted as an escaped 'n', and not as the newline escape character:

$ print X\nY\nZ 
XnYnZ 

The \ character can also be used to quote the escape characters:

$ print X\\nY\\nZ 
X 
Y 
Z 

A tab can be displayed with the \t escape character:

$ print "X\tY\tZ" 
X     Y      Z 

The \c escape character causes the trailing newline to be dropped from the output. It is often used to create prompts.

$ print "Enter choice: \c" 
Enter choice: $ 

Notice that the command prompt was displayed following the argument, and not on the next line.

The \r escape character causes a carriage return without line feed to be displayed and can be used to format non-fixed length data. This command prints an R on the right side, then the \r escape character moves the cursor back to the beginning of the same line and prints an L on the left side followed by TEXT:

$ print      '                R\rLTEXT' 
LTEXT        R 
$ print '               R\rL      TEXT' 
L        TEXTR 

Notice that the L and R characters are lined up, while TEXT is in a different position in both commands. In the Bourne shell, this is the easiest way to display non-fixed-length data in a fixed-length format. In the Korn shell, the same display could be created by using a fixed-length variable. First, variable X is set to ten-character wide, left-justified with a value of TEXT. Notice that {}'s must be given to delimit the variable name X:

$ typeset —L10 X=TEXT 
$ print "L${X}R" 
LTEXT        R 

To right-justify the value of X, the right-justify attribute is set:

$ typeset —R10 X 
$ print "L${X}R" 
L             TEXTR 

This print command displays a message and beeps:

$ print "Unexpected error!\a" 
Unexpected error!<BEEP> 

Using octal codes, the previous command could be given like this:

$ print "Unexpected error!\007" 
Unexpected error!<BEEP> 
print Options

The print command has a number of options that affect the way its arguments are interpreted. The ?/span> option is used if you want to print arguments that begin with a ?/span> character. In the next command, without the ?/span> argument, –Z is interpreted as an option and causes an error to be returned:

$ print —Z 
/bin/ksh: print: bad options(s) 
$ print ?—Z 
?span class="docEmphStrong">Z 

The –n option is used when you don't want the trailing newline to be printed:

$ print —n "Enter choice:" 
Enter choice:$ 

Figure print Options

?/span>

treat everything following ?/span> as an argument, even if it begins with ?

–n

do not add a ending newline to the output.

–p

redirect the given arguments to a co-process.

–r

ignore the \ escape conventions.

–R

ignore the \ escape conventions; do not interpret ?/span>arguments as options (except –n).

–s

redirect the given arguments to the history file.

–un

redirect arguments to file descriptor n. If the file descriptor is greater than 2, it must first be opened with the exec command. If n is not specified, the default file descriptor is 1.

This is equivalent to:

$ print "Enter choice:\c" 
Enter choice:$ 

The –r option causes the special escape characters to be ignored. Here, \t is printed without interpretation as the tab character:

$ print —r 'a\tb' 
a\tb 

The –R option is the same as –r, except that it also causes arguments beginning with ?/span> (except –n) to be interpreted as regular arguments and not as options.

$ print —R ?'—a\tb' 
?—a\tb 

The –s option redirects the given arguments to the history file.

$ print —s "This is a history entry" 
$ history ? 
165   This is a history entry 
166   history ? 

The –u option is used to redirect arguments to a specific file descriptor. Instead of displaying a message to standard error like this:

$ print "This is going to standard error >&2 
This is going to standard error 

the print –u2 command can be used.

$ print —u2 "This is going to standard error" 
This is going to standard error 

The echo Command

The echo command displays its arguments on standard output and is provided for compatibility with the Bourne shell. In the Korn shell, echo is an exported alias set to "print ?/span>".

The exec Command

The exec command is used to perform I/O redirection with file descriptors 0 through 9 using this format:

exec I/O-redirection-command 

The I/O redirection performed by the exec command stays in effect until specifically closed, changed, or if the script or shell terminates. We could use this idea to direct standard output to a file. First, let's start a subshell. You know that 1>std.out directs standard output to std.out. So if we put the exec command in front of it, all subsequent standard output will be redirected.

$ ksh 
$ exec 1>std.out 

Now anything that goes to standard output is redirected to std.out until file descriptor 1 is specifically reset.

$ pwd 
$ whoami 
$ print "Where is this going?" 

Notice that standard error is still attached to your terminal:

$ print —u2 "This is going to standard error" 
This is going to standard error 

Let's exit from the subshell, and take a look at the output file:

$ exit 
$ cat std.out 
/home/anatole/bin 
anatole 
Where is this going? 

Here, file redir.out is opened as file descriptor 5 for reading and writing:

$ exec 5<>redir.out 

Now the print command writes something to file descriptor 5:

$ print —u5 "This is going to fd 5" 

and the cat command reads from it:

$ cat <&5 
This is going to fd 5 

To finish up, we use another exec to close file descriptor 5:

$ exec 5<&?/span> 

Any subsequent attempts to write to it or read from it would generate this error message:

$ print —u5 "Trying to write to fd 5 again" 
/bin/ksh:  5: bad file unit number 

Standard input can be taken from a file like this:

exec 0<file 

Commands could be read in from file, and it would be almost as if you typed them at your terminal.

The exec command can also be used to replace the current program with a new one. For example, you know that if you wanted to run the C shell, you could invoke it as a subshell like this:

$ csh 
{aspd:1} 

But why have the extra parent shell process hanging around if you don't need it? In this case, the exec command could be used to replace the current shell with the C shell:

$ exec csh 
{aspd:1} 

Now if you exited from the C shell, you would be logged out.

Here is another application. Remember the smenu script from the select command section? You could make a full-blown UNIX interface menu out of it by adding some more commands. If you wanted to set it up as a login shell, this would need to be added to a .profile file:

exec smenu 

and execution would be restricted to smenu.

The read Command

The read command is used to read input from a terminal or file. The basic format for the read command is:

read variables 

where a line is read from standard input. Each word in the input is assigned to a corresponding variable, so the first variable gets the first word, the second variable the second word, and so on. Here, "This is output" is read in to the variables X, Y, and Z. The first word of the input is This, so it is assigned to the first variable X. The second word is is, so it is assigned to the second variable Y. The third word is output, so it is assigned to Z.

$ print "This is output" | read X Y Z 
$ print $X 
This 
$ print $Y 
is 
$ print $Z 
output 

If there aren't enough variables for all the words in the input, the last variable gets all the remaining words. This command is the same as the last one, except that an extra string "again" is given.

$ print "This is output again " | read X Y Z 
$ print $X 
This 
$ print $Y 
is 

Figure read Options

–p

read input line from a co-process

–r

do not treat \ as the line continuation character

–s

save a copy of input line in the command history file

–un

read input line from file descriptor n. If the file descriptor is greater than 2, it must first be opened with the exec command. If n is not specified, the default file descriptor is 0.

Because there are four strings, but only three variables, Z gets the remaining unassigned words:

$ print $Z 
output again 

If one variable argument is given to the read command, it gets assigned the entire line. Here, the variable LINE is set to the entire line:

$ print "This is output again" | read LINE 
$ print $LINE 
This is output again 

The kuucp script could be modified so that it prompted for a source file and target system. This is just a bare-bones script that demonstrates the use of the read command. A usable version is included in Appendix D.

$ cat kuucp 
PUBDIR=${PUBDIR:?usr/spool/uucpublic} 

# Prompt for source file 
print —n "Enter source file: " 
read SOURCE 

# Prompt for remote system name 
print —n "Enter remote system name: " 
read RSYS 

print "Copying $SOURCE to $RSYS!$PUBDIR/$SOURCE" 
uucp $SOURCE $RSYS!$PUBDIR/$SOURCE 

Here is some sample output:

$ kuucp 
Enter source file: rt.c 
Enter remote system name: mhhd 
Copying rt.c to mhhd!/usr/spool/uucppublic/rt.c 
Reading Input from Files

Besides reading input from your terminal, the read command is also used to read input from a file. The read command by itself will only read one line of input, so you need a looping command with it. To read in the contents of a file, use this format:

exec 0<file 
while read variable 
do 
       commands 
done 

The exec command opens file for standard input, and the while command causes input to be read a line at a time until there is no more input. If the exec open on file fails, the script will exit with this error message:

script-name: file: cannot open 

Here is a stripped-down version of kcat. It is a simple version of the UNIX cat command. It displays the given file on standard output one line at a time.

$ cat kcat 
exec 0<$1 
while read LINE 
do 
      print $LINE 
done 

In terms of performance, it is about 3-4 times slower than the UNIX cat command, but it will do for demonstration purposes. Here is sample output:

$ kcat test.input 
1: All work and no play makes Jack a dull boy. 
2: All work and no play makes Jack a dull boy. 
3: All work and no play makes Jack a dull boy. 
. . . 

The real version of the kcat command is listed in Appendix D.

Here is an alternate format that will also work for reading input from files:

cat file | while read variable 
do 
        commands 
done 

On the systems tested, the exec format for reading input from files was about 40-60 times faster than the last version above. It may be different on your system, but that's still a significant performance improvement.

The IFS Variable

The read command normally uses the IFS (Internal Field Separator) variable as the word separators. The default for IFS is space, tab, or newline character, in that order, but it can be set to something else. It is useful for when you want to read data that is not separated with whitespace. In this example, IFS is set to a comma:

$ IFS=, 

then the print arguments are separated with the new word separator for read:

$ print 'This,is,output' | read WORD1 WORD2 WORD3 
$ print $WORD1 $WORD2 $WORD3 
This is output 

By setting IFS to :, the fields in the /etc/passwd file could be read into separate variables.

$ cat ifs_test 
IFS=: 
exec 0</etc/passwd 
while read -r NAME PASS UID GID COMM HOME  SHELL 
do 
      print "Account name=    $NAME 
Home directory=    $HOME 
Login Shell= $SHELL" 
done 

Here is sample output:

$ ifs_test 
Account name=root 
Home directory= / 
Login Shell= /bin/ksh 
Account name=anatole 
Home directory= /home/anatole 
Login Shell= /bin/ksh 
. . . 
More with read

Another format for the read command is:

read options [variables] 

where input is read and assigned to variables according to the given options. The ?span class="docEmphStrong">u option is used to read input from a specific file descriptor. If the file descriptor is greater than 2, then it must first be opened with the exec command. Let's look at the stripped-down version of kcat again. It could be changed to prompt to continue before displaying the next line like this:

$ cat kcat 
exec 0<$1 
while read LINE 
do 
      print $LINE 
      print —n "Do you want to continue?" 
      read ANSWER 
      [[ $ANSWER = @([Nn])* ]] && exit 1 
done 

Here is the test.input file again:

$ cat test.input 
1: All work and no play makes Jack a dull boy. 
2: All work and no play makes Jack a dull boy. 
3: All work and no play makes Jack a dull boy. 
. . . 

When the new version of kcat is run, the output looks strange. Can you figure out the problem? The reason is that we redirected standard input from test.input, but we are also expecting the input to ANSWER from standard input. In the first loop, line 1 from test.input is assigned to LINE. The next read command, which is inside the loop, reads the next line into ANSWER. In the second loop, we're up to line 3, so it gets assigned to LINE, and so on.

$ kcat test.input 
1: All work and no play makes Jack a dull boy. 
Do you want to continue?3: All work and no play 
makes Jack a dull boy. 
Do you want to continue? 
. . . 

Instead of redirecting standard input (file descriptor 0) from test.input, we could redirect it from another file descriptor and read it using the –u option. Then it wouldn't interfere with read ANSWER which is expecting input from standard input. Here is the new and improved version:

$ cat kcat 
exec 4<$1 
while read -u4 LINE 
do 
      print $LINE 
      print —n "Do you want to continue?" 
      read ANSWER 
      [[ $ANSWER = @([Nn])* ]] && exit 1 
done 

Now it works.

$ kcat test.input 
1: All work and no play makes Jack a dull boy. 
Do you want to continue?<RETURN> 
2: All work and no play makes Jack a dull boy. 
Do you want to continue?<RETURN> 
3: All work and no play makes Jack a dull boy. 
Do you want to continue?n 
$ 

The –s option saves a copy of the input in the history file. Here is an example:

$ print "This is a history entry" | read —s HVAR 

Just to make sure, let's look at both the history file:

$ history ? 
170   This is a history entry 
171   history ? 

and HVAR:

$ print $HVAR 
This is a history entry 
Reading Input Interactively

The read command allows input to be read interactively using this format:

read name?prompt 

where prompt is displayed on standard error and the response is read into name. So instead of using two commands to display a prompt and read the input:

$ print —n "Enter anything: " 
$ read ANSWER 

The same thing can be done with one command.

$ read ANSWER?"Enter anything: " 
Enter anything: ANYTHING 

Here is ANSWER:

$ print $ANSWER 
ANYTHING 

Let's change the kuucp script (again) so that this format of the read command was used:

$ cat kuucp 
PUBDIR=${PUBDIR:?usr/spool/uucpublic} 

read SOURCE?"Enter source file: " 
read RSYS?"Enter remote system name: " 

print "Copying $SOURCE to $RSYS!$PUBDIR/$SOURCE" 
uucp $SOURCE $RSYS!$PUBDIR/$SOURCE 
The REPLY variable

If no variables are given to the read command, the input is automatically assigned to the REPLY variable. Here, ANYTHING is read into REPLY:

$ print ANYTHING | read 
$ print $REPLY 
ANYTHING 


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows