Version 13 of Pipes vs Expect

Updated 2002-10-17 23:09:10

the page is to discuss ways in which pure-Tcl can be used to do some of the simpler things one might otherwise want to use Expect for. For example, in CVS it is stated that:


...ways to use cvs...

      [open "|cvs -d $cvsroot init" RDWR] and talking to
      cvs along pipes.

Will this really work? If cvs asks for my password, can I really check for that and send it back down the pipe?

Well, yes, you can. If however there is going to be very much interaction, or if the strings being examined are going to be very complex, I (LV) would recommend looking at Expect as a Tcl extension to make the interaction a bit easier.


Now I can get this to work with something like 'aspell' which is a standalone process, but I find that when trying to do this on 'cvs', that cvs fires up another process (rsh) which wants the password, but Tcl doesn't know anything about that... So cvs is sitting there waiting for rsh which is waiting for a password (I assume -- certainly it is waiting for something!), and since no password is supplied, everything eventually times out and fails.

I understand that for complex uses Expect is obviously the way to go, but if all I want to do is make sure 'cvs' gets my password, it sounds like it would be easy enough just to use pure Tcl. Here's a code outline:

    cd c:/tcl-source/tcl
    catch {
        console show
        update
    }

    proc go {} {
        set pipe [open "|cvs -z5 update ChangeLog" RDWR]
        fconfigure $pipe -buffering line -blocking 0
        fileevent $pipe readable [list piperead $pipe]
        return $pipe
    }

    proc piperead {pipe args} { 
        if {![eof $pipe]} {
            puts "read $pipe : $args" 
            set got [gets $pipe]
            puts "got: $got"
            if {[string first "password" $got] != -1} {
                puts $pipe "my password"
            }
        }
    }

    go

Something like the above can be made to work to interact with 'aspell' to perform spellchecking, but when using cvs, it fires up a separate 'rsh' process which is looking for input, but Tcl doesn't know that, so the above doesn't work.

Any ideas to solve that problem?


I had to solve this today :) Here's what I came up with:

set cat32 file join [file dirname [info name] cat32.exe

proc tryexec {args} {

    global done cat32

    puts "calling: $args"
    set pipe [open "|$args |& $cat32" r]
    puts "pid(s) [pid $pipe] started... please wait..."
    fconfigure $pipe -buffering line -blocking no
    fileevent $pipe readable [list gotpipereadable $pipe]
    set done 0
    vwait done
    puts "done!"

}

proc gotpipereadable {pipe} {

    global done

    set got [gets $pipe]
    if {[string length $got]} {puts "*** $got"}

    ### check for incompleted lines
    if {[fblocked $pipe]} {
        puts -nonewline "*** [read -nonewline $pipe]"
        flush stdout
    }

    if {[eof $pipe]} {

        set done 1
        set status [catch {close $pipe} result]

        if {$status == 0} {

            # The command succeeded, and wrote nothing to stderr.
            # $result contains what it wrote to stdout, unless you
            # redirected it

        } elseif {[string equal $::errorCode NONE]} {

            # The command exited with a normal status, but wrote something
            # to stderr, which is included in $result.

        } else {

            switch -exact -- [lindex $::errorCode 0] {

                CHILDKILLED {

                    foreach { - pid sigName msg } $::errorCode break
                        # A child process, whose process ID was $pid,
                        # died on a signal named $sigName.  A human-
                        # readable message appears in $msg.

                }

                CHILDSTATUS {

                    foreach { - pid code } $::errorCode break

                        # A child process, whose process ID was $pid,
                        # exited with a non-zero exit status, $code.

                            puts "pid $pid exited with code: $code"
                        #puts "stderr: $result"

                }

                CHILDSUSP {

                    foreach { - pid sigName msg } $::errorCode break
                        # A child process, whose process ID was $pid,
                        # has been suspended because of a signal named
                        # $sigName.  A human-readable description of the
                        # signal appears in $msg

                }

                POSIX {

                    foreach { - errName msg } $::errorCode break
                        # One of the kernel calls to launch the command
                        # failed.  The error code is in $errName, and a
                        # human-readable message is in $msg.

                }
            }
        }
    }

}

proc DoCvsLogin {project} {

    global Config projectData
    tryexec $Config(cvsprog) -d:pserver:anonymous@$projectData($Config(project),cvshome) login

}

It isn't perfect, but kinda close ;) Note that cat32.exe is part of the tcltest target when building on windows. and |& combines both stdout and stderr together going into cat32 just being a pass-through into Tcl. cat32 always exits with data put to stderr, so close throws an errorCode of CHILDSTATUS.

DG


Category Expect