Search code examples
tclexpect

Making Tcl/Expect statements less "greedy"


Consider the Expect script below. It creates a test file with a character on each line (a,b,c,a,b,c), and opens the file using spawn:

#!/usr/bin/expect -f

proc create_test_file {fname} {
    set chan [open $fname w]
    puts $chan "a\nb\nc"
    puts $chan "a\nb\nc"
    catch {close $chan}
}

log_user 0
create_test_file "test_file.txt"

set f [open "test_file.txt" r]
spawn -open $f

while {1} {
    expect {
        "c" { send_user "C\n" }
        "b" { send_user "B\n" }
        "a" { send_user "A\n" }
        eof { break }
    }
}

The output is:

C
C

The output is expected as expect doesn not process the file line-by-line but rather all at once. The first line in the expect statement "c" will discard the first lines with a and b and then match the third line with c, and so on for the next loop.

The other expect statements would also match the lines but they never get a chance to do so as the first statement matches first, even though the first statement discards more of the lines.

I think I understand this behavior, but now my question: Is there any way to tweak expect's behavior so that the expect statement that matches is the one that needs to discard the least amount of lines in the file? So that the produced output would become the following:

A
B
C
A
B
C

Solution

  • By default patterns are unanchored. You can anchor them to the start of the buffer as regular expressions using ^. For example, to match char by char, skipping over unmatched chars with regexp .:

    while {1} {
        expect {
            -re "^c" { send_user "C\n" }
            -re "^b" { send_user "B\n" }
            -re "^a" { send_user "A\n" }
            -re "^." { exp_continue }
            eof { break }
        }
    }
    

    Although I used regular expressions above, it wasn't necessary in this case as the default "glob" patterns also accept ^ to mean the start of the buffer, so you could also use

            "^c" { send_user "C\n" }
            "^b" { send_user "B\n" }
            "^a" { send_user "A\n" }
            "^?" { exp_continue }
    

    where ? matches a single character in a glob pattern, equivalent to the . in a regexp.