Well, as I indicated above,
exec blocks until the program it runs finishes execution. So it sounds as though
exec isn't going to fit your needs in this case. Instead, let's turn our attention to
interprocess pipes.
In Tcl, you can create a pipe to a program with the
open command. But instead of giving the name of a file to open, you give a "|" character folowed by the name of a program you want to execute. For example:
[tt]set fid [ignore][[/ignore]open "
| myprog arg1 arg2" r+[ignore]][/ignore][/tt]
When Tcl executes an
open command like this, it starts the program specified, and automatically sets up interprocess communication pipes to it. The return value of
open is a
channel identifier that you use in exactly the same way as a file identifier when you've opened a regular file. You can use
gets to read output a line at a time, and you can use
puts to send information to the program. But as far as the program executed is concerned, it's just reading from its standard input and writing to its standard output channels. So, to process the output of a program, you could do the following:
Code:
set fid [open "| myprog" r]
while {[gets $fid line] != -1} {
# gets returns -1 if it encounters the
# end of output from the program.
# If it returns > -1, then we've read
# a line of output and the characters
# are stored in the variable "line".
# Process the line read as desired...
}
# Close our side of the pipe when we're done
close $fid
Writing is a bit trickier, because Tcl (like virtually all programs that use the standard I/O libraries) by default doesn't immediately send characters to the other program as soon as you
puts them. Instead it buffers them until it's got a big block of data (typically 4KB) to send. This is usually more efficient, but not appropriate if we're interacting with another program that's expecting input a line at a time.
There are two solutions. One is to use the
flush command to force Tcl to immediately send any characters written with
puts. For example:
[tt]puts $fid "Here's a line of input"
flush $fid[/tt]
Another option is to use the
fconfigure command to change the communication settings of the channel. In particular, we can set the
-buffering option to "line", which forces Tcl to automatically flush its buffer after every complete line written. For example:
[tt]set fid [ignore][[/ignore]open "| myprog" r+[ignore]][/ignore]
fconfigure $fid -buffering line
# Now each line written with
puts is
# immediately sent to the other program
# automatically.
puts $fid "Here's your first line."
puts $fid "And a second line..."[/tt]
There are still opportunities for Tcl to block, even with this approach. Specifically,
gets will block if there's not a complete line of data to read. It will return only when there's a complete line available. Also, you need to be careful if you're integrating code like this into a GUI program. It's easy to get the GUI to lock up while you're trying to read and process data. To prevent this, you need to go to an
event-driven approach, where you use the
fileevent command to register
callbacks, which allow you to be notified when data is available to read, and handle it at that time.
Event-driven I/O is yet another lengthy topic (at least if you do it correctly). That's why I've got a 1-day course entirely devoted to interprocess communication in Tcl using pipes and sockets. The basics are easy, but fitting them all together into a robust, elegant solution requires a bit of thought and planning.
Still, it's a
lot easier doing this in Tcl than it is in Java, or even Perl... - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax