Tcl Tutorial Lesson 26a

Communicating with other programs - fileevent, after

You can use the fileevent (or chan event in Tcl 8.5) to continuously communicate with another program. If that program takes input from the prompt (standard input), you can send it data or commands. Output to the screen (standard output) can be caught and handled.

Here is a simple example. The program we are going to run is this:

# calc.tcl --
#     Demo program
#
for {set i 0} {$i < 10} {incr i} {
    after 1000
    puts $i
    flush stdout
}

We use the after command to have it wait a little (1000 milliseconds, actually) before printing the next line. To ensure that the short line that is written is actually sent to the standard output rightaway, we use the flush statement. It may or may not be necessary - using it is safest.

The counter part is the following program:

proc getline {chan} {
    global done

    gets $chan line

    if { ! [eof $chan] } {
        puts ">> $line"
    } else {
        close $chan
        set done 1
    }
}

set program [open "|tclsh calc.tcl" r]
fileevent $program readable [list getline $program]
fconfigure $program -buffering line

vwait done

Central in this program is the procedure getline. It takes a single argument, the channel to read from. When it is invoked, it reads a single line and echos it to the screen (standard output). If an end-of-file condition occurs, it closes the channel - the connection to the external process.

Now the external process is started using the open command, instead of exec, so that we can communicate with it (here: only catch the output to standard output). The channel, in the variable program, is prepared for the type of communication we want via the fconfigure command, but it is not entirely necessary.

When the end-of-file condition occurs - the other program stopped or closed its standard output channel, the channel in the controlling program is closed, but also the global variable done is set. This signals the vwait command to complete: until the variable done is set, it will wait and process any events.

Interrupting the other program

Suppose the other program takes a much longer time to run than you find acceptable. You can schedule code to run after a certain period so that the other program is killed. Unfortunately killing another program is a system-dependent action, using a command like kill on Linux, but we can close the channel to the program:

# Close the channel after 100 seconds ...
after 100000 {
    catch {
        close $program
    }
    set done 1
}

The catch is necessary to make sure that the error reported by the other program on standard error does not interfer with our steering program (data written to standard error causes this). And out of laziness we have not used a proper procedure, but normally you would put code that is to be run via the after command in procedures to avoid quoting and scope problems. Such code runs in the global namespace.

Besides killing another program or severing the connection, you can also use the after command as a way to send events to your program, enabling an event-driven programming style.