19. Input/Output Commands

19.1 Escape Characters

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.

19.2 print Options

19.3 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.

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<&—

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.


19.4 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
    $ print $Z
    output again

19.4.1 Reading Input from Files

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.


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.


19.4.2 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 snewline character, in that order, but it can be set to something else.
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


19.4.3 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


19.4.4 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


20 Miscellaneous Programming Features

20.1 The . Command

The . command reads in a complete file, then executes the commands in it as if they were typed in at the prompt. This is done in the current shell, so any variable, alias, or function settings stay in effect. It is typically used to read in and execute a profile, environment, alias, or functions file. Here the .profile file is read in and executed:
$ . .profile

The following example illustrates the difference between executing files as Korn shell scripts and reading/executing them using the .command. The .test file sets the variable X:
    $ cat .test
    X=ABC
When the .test file is executed as a Korn shell script, variable X is not defined in the current environment, because scripts are run in a subshell:
    $ ksh .test
    $ print $X
    $
After the .test file is read in and executed using the . command, notice that the variable X is still defined:
    $ . .test
    $ print $X
    ABC
The standard search path, PATH, is checked if the file is not in the current directory.


20.2 Functions

Functions are most efficient for commands with arguments that are invoked fairly often, and are defined with the following format:

        function name {
                commands
        }

To maintain compatibility with the Bourne shell, functions can also be declared with this POSIX-style format:
        function-name() {
                commands
        }

These types of functions have many limitations compared to Korn shell style functions, such as no support for local variables.


20.3 Scope & Availability

By default, functions are not available to subshells. This means that a regular function that was read in your working environment, .profile file, or environment file would not be available in a Korn shell script.

To export a function, use the typeset -fx command:
        typeset -fx function-name

To make a function available across separate invocations of the Korn shell, include the  typeset -fx function-name command in the environment file.


20.4 Function Variables

All function variables, except those explicitly declared locally within the function with the typeset command, are inherited and shared by the calling Korn shell script. In this example, the X, Y, and Z variables are set within and outside of the function f:

    $ cat ftest
    X=1
    function f {
        Y=2
        typeset Z=4
        print "In function f, X=$X, Y=$Y, Z=$Z"
        X=3
    }
    f
    print "Outside function f, X=$X, Y=$Y, Z=$Z"

Notice that when executed, all the variable values are shared between the function and calling script, except for variable Z, because it is explicitly set to a local function variable using the typeset command.
The value is not passed back to the calling Korn shell script:
    $ ftest
    In function f, X=1, Y=2, Z=4
    Outside function f, X=3, Y=2, Z=

The current working directory, aliases, functions, traps, and open files from the invoking script or current environment are also shared with functions.


20.5 Displaying Current Functions

The list of currently available functions are displayed using the typeset -f command.


20.6 Autoloading Functions

To improve performance, functions can be specified to autoload. This causes the function to be read in when invoked, instead of each time a Korn shell script is invoked, and is used with functions that are not invoked frequently. To define an autoloading function, use the typeset -fu function-name command. Here, lsf is made an autoloading function:
    $ typeset —fu lsf

The autoload alias can also be used to define an autoloading function. On most systems, it is preset to typeset -fu.

The FPATH variable which contains the pathnames to search for autoloading functions must be set and have at least one directory for autoloading functions to work.


20.7 Discipline Functions

Discipline functions are a special type of function used to manipulate variables. They are defined but not specifically called. Rather, they are called whenever the variable associated with the function is accessed.

There are some specific rules as to how discipline functions are named and accessed. First of all, discipline functions are named using this syntax:
        name.function

Notice the the funcion has two parts separated with a dot. The first part name corresponds to the name of a variable, and the second part must be getset, or unset. These correspond to the following operations on the variable:

  • get     whenever the base discipline variable is accessed
  • set     whenever the base discipline variable is set
  • unset     whenever the base discipline variable is unset


For example, the discipline function LBIN.get, LBIN.set, LBIN.unset is called whenever the variable LBIN is accessed, set, or unset.

All three discipline functions are optional, so not all need to be specified. Within a discipline function, the following special reserved variables can be used:

  • .sh.name     name of current variable
  • .sh.value     value of the current variable
  • .sh.subscript     name of the subscript (if array variable)


From a practical perspective, discipline functions are often used to help debug by tracing the setting and current value of variables in running scripts. Here is a function that can be used to trace setting the value of X:
    function X.set {
        print "DEBUG: ${.sh.name} = ${.sh.value}"
    }

Discipline functions are also a good place to centralize your variable assignment validation rules. Here is a function that checks to make sure that X it set ao a number between 3 and 10:
    function X.set {
        if (( .sh.value<3 || .sh.value >10 ))
        then
            print "Bad value for ${.sh.name}: ${.sh.value}"
        fi
    }

Note that builtin functions can also be used as additional discipline functions.


20.8 FPATH

The FPATH variable contains a list of colon-separated directories to check when an autoloading function is invoked. It is analogous to PATH and CDPATH, except that the Korn shell checks for function files, instead of commands or directories. Each directory in FPATH is searched from left-to-right for a file whose name matches the name of the function. Once found, it is read in and executed in the current environment. With the following FPATH setting, if an autoloading function lsf was invoked, the Korn shell would check for a file called lsf in /home/anatole/.fdir, then /etc/.functions, and if existent, read and execute it:

    $ print $FPATH
    /home/anatole/.fdir:/etc/.functions

There is no default value for FPATH, so if not specifically set, this feature is not enabled.


20.9 Removing Function Definitions

Functions are removed by using the unset -f command. Here, the rd function is removed:
    $ unset -f rd


20.10 Traps

The trap command is used to execute commands when the specified signals are received.
        trap commands signals

Trap commands are useful in controlling side effects from Korn shell scripts. For example, if you have a script that creates a number of temporary files, and you hit the <BREAK> or <DELETE> key in the middle of execution, you may inadvertently leave the temporary files. By setting a trap command, the temporary files can be cleaned up on an error or interrupt.

The trap_test script creates some files, then removes them when an interrupt is received. Notice that the trap command is surrounded in single quotes. This is so that the FILES variable is evaluated when the signal is received, not when the trap is set.
    $ cat trap_test
    trap 'print "$0 interrupted - removing temp files" ;/
    rm —rf $FILES; exit 1' 1 2
    FILES="a b c d e f"
    touch $FILES
    sleep 100
    $ trap_test
    Ctl-c
    trap_test interrupted - removing temp files

If an invalid trap is set, an error is generated.


20.10.1 Ignoring Signals

The trap command can be used to ignore signals by specifying null as the command argument:
        trap  ""  signals
This could be used to make all or part of a Korn shell script uninterruptable using normal interrupt keys like Ctl-c.


20.10.2 Resetting Traps

The trap command can also be used to reset traps to their default action by omitting the command argument:
    trap - signals
or
    trap signals


20.10.3 Exit & Function Traps

A trap can be set to execute when a Korn shell script exits. This is done by using a 0 or EXIT as the signals argument to the trap command:
        trap 'commands0
or
        trap '
commandsEXIT
This could be used to consolidate Korn shell script cleanup functions into one place.

20.10.4 Trap Signal Precedence

If multiple traps are set, the order of precedence is:

  • DEBUG
  • ERR
  • Signal Number
  • EXIT



20.10.5 Trapping Keyboard Signals

The Korn shell traps KEYBD signals (sent when you type a character) and automatically assigns the following reserved variables:

  • .sh.edchar     contains last character of key sequence
  • .sh.edtext     contains current input line
  • .sh.edmode     contains NULL character (or escape character is user in command mode)
  • .sh.edcol     contains position within the current line



20.11 Debugging Korn Shell Scripts

The Korn shell provides a number of options that are useful in debugging scripts: noexecverbose, and xtrace. The noexec option causes commands to be read without being executed. It is used to check for syntax errors in Korn shell scripts. The verbose option causes the input to be displayed as it is read. The xtrace option causes the commands in a script to be displayed as they are executed.


20.11.1 Debugging with trap

The trap command can also be helpful in debugging Korn shell scripts. The syntax for this type of trap command is:
        trap commands DEBUG
or
        trap commands ERR

20.12 Co-Processes

Co-processes are commands that are terminated with a |& character. They are executed in the background, but have their standard input and output attached to the current shell. The print -p command is used to write to the standard input of a co-process, while read -p is used to read from the standard output of a co-process. Here, the output of the date command is read into the DATE variable using the read -p command:
    $ date |&
    [2] 241
    $ read —p DATE
    $ print $DATE
    Thu Jul 18 12:23:57 PST 1996
Co-processes can be used to edit a file from within a Korn shell script.

THE END