Well, the easiest way would be to simply
exec your commands instead of using
open to create a pipe to the command. By default,
exec blocks until the command finishes, so you could just use several
exec commands to run your programs in sequence.
However, this isn't an acceptable solution when you've got a GUI program, because while
exec is blocking, Tcl can't get into the event loop to process events. The effect is that your application is locked until the program finishes, which annoy users no end.
The best way to handle this in pure Tcl will be to change your program so that it doesn't use a
while loop to start all the programs. Instead, one strategy is to write a procedure that somehow takes a command list and starts only one of them at a time. We'll then arrange to keep calling this procedure each time a command finishes. You know when the command for an
open pipe ends because you get an EOF condition on your pipe. (This is usually a safe assumption, although there are a few programs out there that close their output channels and keep on executing.) You should already be testing for EOF in your
fileevent handlers, and closing the pipe in response. EOF is a
persistant readable event, so if you don't
close the pipe in response, your
fileevent handle immediately gets called again and again in an endless loop.)
There are several different implementations you could use. Here's a simple (untested) one that comes to mind. We'll use a global variable called
cmdList to store a list of commands to execute in sequence. We'll create a procedure called
RunOne that starts the first command on the list, and then removes it from the list. If
cmdList is empty,
RunOne simply returns. We'll call
RunOne once to start the sequence of execution. Then we'll call
RunOne again whenever we get an EOF in our
fileevent handler.
Code:
proc RunOne {} {
global cmdList
global input
# As long as there are commands to run,
# start the next one.
if {[llength $cmdList] != 0} {
# Start the command
if {[catch {open "|$command"} input]} {
.t insert end "***error: $errmsg\n"
} else {
fileevent $input readable Addtext
}
# Remove it from the list
set cmdList [lreplace $cmdList 0 0]
}
}
proc Addtext {} {
global input
# Read a line and process it
if {[catch {gets $input line} num]} {
# We got an error trying to read a line.
# Close the channel and start the next
# command.
catch {close $input}
# Do whatever error handling you want here
RunOne
} elseif {$num == -1} {
# We received EOF. Close the channel and
# start the next command.
catch {close $input}
# Do whatever EOF handling you want here
RunOne
} else {
# We simply read a line of text. Do
# whatever you need to do.
}
}
# Set up the command list and start the
# first command in the list.
set cmdList [list]
lappend cmdList "The first command to run"
lappend cmdList "The second command to run"
lappend cmdList "The third command to run"
RunOne
Another possibility would be to use the
bgexec command from the
BLT extension.
bgexec allows you to start a program using very similar syntax to
exec. However,
bgexec can run the program in the background -- so your application can still handle events -- while still capturing the output of the command. You can also receive notification when the command exits. You can find out more information about BLT on the Tcl'ers Wiki (
on the page "BLT,"
from the BLT homepage,
and from the BLT SourceForge page,
- Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax