. Invoking Subprocesses from Tcl - exec, open .
[ Previous | Index | Next ]
So far the lessons have dealt with programming within the Tcl interpreter. However, Tcl is also useful as a scripting language to tie other packages together. To accomplish this function, Tcl has methods for invoking subprocesses.
There are two ways to invoke a subprocess from Tcl:
- open ...... run a new program with I/O connected to a file descriptor
- exec ...... run a new program as a subprocess
The open call is the same call that is used to open a file. If the first character in the file name argument is a pipe symbol (|), then open will treat the rest of the argument as a program name, and will exec that program with the standard input or output connected to a file descriptor. A pipe can be opened to a sub-process for reading, writing or both reading and writing.
If the file is opened for both reading and writing you must be aware that the pipes are buffered. The output from a puts command will be saved in an I/O buffer until the buffer is full, or until you execute a flush command to force it to be transmitted to the subprocess. The output of the subprocess will not be available to a read or gets until the I/O buffer for the subprocess has filled its output buffer.
The exec call is similar to invoking a program (or a set of programs piped together) from the shell prompt or in a unix shell script. It supports several styles of output redirection, or it can return the output of the sub-process as the return of the exec call.
- open |progName ?access?
- Returns a file descriptor for the pipe. The progName argument must start with the pipe symbol. If progName is enclosed in quotes or braces, it can include arguments to the subprocess.
- exec ?switches? arg1 ?arg2? ... ?argN?
- Exec treats its arguments as the names and arguments for a set of subprocesses to execute. If the first args start with a "-", then they are treated as switches to the exec command, instead of being invoked as subprocesses or subprocess options.
Switches are:
- -keepnewline
- Retains a trailing newline in the pipeline's output. Normally a trailing newline will be deleted.
- --
- Marks the end of the switches. The next string will be treated as arg1, even if it starts with a "-"
Arg1 - argN can be one of:
- the name of a program to execute
- an command line argument for the subprocess
- an I/O redirection instruction.
There are many permutations to the I/O redirection commands. The main subset of these commands is:
- |
- Pipes the standard output of the command preceeding the pipe symbol into the standard input of the command following the pipe symbol.
- < fileName
- The first program in the pipe will read input from fileName.
- < @ fileID
- The first program in the pipe will read input from the Tcl descriptor fileID. FileID is the value returned from an open ... "r" command.
- < < value
- The first program in the pipe will read value as its input.
- > fileName
- The output of the last program in the pipe will be sent to fileName. Any previous contents of fileName will be lost.
- > > fileName
- The output of the last program in the pipe will be appended to fileName.
- 2> fileName
- The standard error from all the programs in the pipe will be sent to fileName. Any previous contents of fileName will be lost.
- 2> > fileName
- The standard error from all the programs in the pipe will be appended to fileName.
- > @ fileID
- The output from the last program in the pipe will be written to fileID. FileID is the value returned from an open ... "w" command.
If you are familiar with shell programming, there are a few differences to be aware of when you are writing Tcl scripts that use the exec and open calls.
- You don't need the quotes that you would put around arguments to escape them from the shell expanding them. In the example, the argument to set is not put in quotes. If it were put in quotes, the quotes would be passed to set, instead of being stripped off (as the shell does), and set would report an error.
- If you use the open |cmd "r+" construct, you must follow each puts with a flush to force Tcl to send the command from its buffer to the program. The output from the subprocess may be buffered in its output buffer. - You can sometimes force the output from the sub-process to flush by sending an exit command to the process. You can also use the fconfigure command to make a channel unbuffered. The expect extension to Tcl provides a much better interface to other programs, which handles the buffering problem.
- If one of the commands in an open |cmd fails the open does not return an error. However, attempting to read input from the file descriptor with gets $file will return an empty string. Using the gets $file input construct will return a character count of -1. - Put quotes around the s/.Q//g in the example to see this behavior.
- If one of the commands in an exec call fails to execute, the exec will return an error, and the error output will include the last line describing the error.
--
. Example .
# Create a unique (mostly) file name for a Tcl program
set tempFileName "TEMPDIR/inv_[pid].tcl"
# Open the output file, and write a simple program to it
set outfl [open $tempFileName w]
puts $outfl {
set len [gets stdin line]
if {$len < 5} {exit -1}
for {set i $len} {$i >= 0} {incr i -1} {
append l2 [string range $line $i $i]
}
puts $l2
exit 0;
}
# Flush and close the file
flush $outfl
close $outfl
# Run the new Tcl file interactively
# Open a pipe to the program
set io [open "|TCL_INTERP $tempFileName" r+]
# send a string to the new program
# *MUST FLUSH*
puts $io "This will come back backwards."
flush $io
# Get the reply, and display it.
set len [gets $io line]
puts "To reverse: 'This will come back backwards.'"
puts "Reversed is: $line"
puts "The line is $len characters long"
# Run the program with input defined in an exec call
set invert [exec TCL_INTERP $tempFileName << \
"ABLE WAS I ERE I SAW ELBA"]
# display the results
puts "The inversion of 'ABLE WAS I ERE I SAW ELBA' is \n $invert"
# Clean up
file delete $tempFileName
|