Driving tclsh with Expect

Summary

A small beginner-level example of using Expect to feed a script to tclsh

The Sequential Version

This script reads a Tcl script and enters it into an interactive tclsh.

#! /bin/env tclsh

package require Expect

proc tclprompt {} {
    proc [lindex [info level 0] 0] {} {
        return {\n%\s$}
    }
    return {%\s*$}
}

set timeout -1
set fh [open [lindex $argv 0]]
set script [split [read $fh][close $fh] \n]
spawn tclsh
set command {}
foreach line $script {
    if {[set line [string trimright $line]] eq {}} {
        continue
    } else {
        append command $line\r
    }
    if {[info complete $command]} {
        expect -re [tclprompt] {
            send $command
            set command {}
        }
    }
    
}
expect -re [tclprompt]

Some interesting points:

Setting timeout to -1 allows expect to wait as long as necessary for the next prompt.

[tclprompt] uses a $ to anchor the prompt to the end of the output. Expect is not line-oriented, so this is not foolproof.

[tclprompt] is implemented as a command rather than a variable because the first prompt is not preceded by a newline character, but subsequent prompts are, and manipulating the proc one time is more succinct than putting an extra conditional into the foreach statement.

simply checking for "%" is less robust than checking for a newline followed by "%" followed by space followed by the end of output. It's generally a good idea to be as specific as possible when expecting a prompt.

The Event-Driven Version

Here is an event-driven version of the same thing (only tested on *nix):

#! /bin/env tclsh

package require Expect

proc tclprompt {} {
    proc [lindex [info level 0] 0] {} {
        return {\n%\s$}
    }
    return {%\s*$}
}

proc input {fh {command {}}} {
    #remove the current event handler since leaving it in place seems to interact poorly with [expect]
    fileevent $fh readable {}
    if {$command ne {} && [info complete $command]} {
        expect -re [tclprompt] {
            send $command
        }
        fileevent $fh readable [list input $fh]
        return
    }

    if {[eof $fh]} {
        set ::done 1
        expect -re [tclprompt] {
            return
        }
    }

    gets $fh line
    if {[set line [string trimright $line]] ne {}} {
        append command $line\r
    }
    fileevent $fh readable [list input $fh $command]
}

spawn tclsh
set timeout -1
set fh [open [lindex $argv 0]]
fconfigure $fh -blocking 0
fileevent $fh readable [list input $fh]
vwait done