使用 Expect 自动化 xterm

Automating xterm using Expect

我正在尝试使用 Expect 使 xterm window 自动化(尽管我已经知道 Expect 无法控制此类 GUI 应用程序,但是在Exploring Expect)

package require Expect 
spawn -pty
stty raw -echo < $spawn_out(slave,name)
regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
if {[string compare $c1 "/"] == 0} {
    set c1 "0"
}
set xterm_pid [exec xterm -S$c1$c2$spawn_out(slave,fd) &]
close -slave
expect "\n" ;# match and discard X window id

set xterm $spawn_id 

spawn $env(SHELL)

Don Libes 提到从这一点开始,xterm 可以自动化,他给出了使用 xterm 和 interact 命令的示例,如下所示,

interact -u $xterm "X" {
    send -i $xterm "Press return to go away: "
    set timeout -1
    expect -i $xterm "\r" {
        send -i $xterm "Thanks!\r\n"
        exec kill $xterm_pid
        exit
    }
}

但是,我的期望是发送和期望命令 to/from xterm。我尝试了以下方法,

send -i $xterm "ls -l\r"; # Prints commands on xterm 
expect -i $xterm "\$" ; # Trying to match the prompt

但是没有锻炼。本例主要依赖xterm的命令行选项-Sccn.

-Sccn

This option allows xterm to be used as an input and output channel for an existing program and is sometimes used in specialized applications. The option value specifies the last few letters of the name of a pseudo-terminal to use in slave mode, plus the number of the inherited file descriptor. If the option contains a "/" character, that delimits the characters used for the pseudo-terminal name from the file descriptor. Otherwise, exactly two characters are used from the option for the pseudo-terminal name, the remainder is the file descriptor. Examples:

-S123/45
-Sab34 

Note that xterm does not close any file descriptor which it did not open for its own use. It is possible (though probably not portable) to have an application which passes an open file descriptor down to xterm past the initialization or the -S option to a process running in the xterm.

我哪里出错了?

这里是我使用的代码的视图。它是从一个复杂的部分中提取出来的。

# create pty for xterm
set spawn(PTTY,PID) [spawn -noecho -pty]
set spawn(PTTY,DEVICE) $spawn_out(slave,name)
set spawn(PTTY) $spawn_id
stty raw -echo < $spawn(PTTY,DEVICE)
regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
if {[string compare $c1 "/"] == 0} { set c1 0 }

# Start XTERM (using -into can place the xterm in a TK widget)
set pid(XTERM) [::exec xterm   -S$c1$c2$spawn_out(slave,fd) {*}$addidtionlXtermOptions  &]
close -slave

# Link
set spawn(SHELL,PID)    [spawn -noecho {*}$commandInXterm]
set spawn(SHELL)        $spawn_id
set spawn(SHELL,DEVICE) $spawn_out(slave,name)

# ...
# send a key or string into the xterm
exp_send -raw -i $spawn(SHELL) --  $key
exp_send -raw -i $spawn(SHELL) -- "$str\r

作为Mr.Thomas Dickey pointed out , I started exploring on the multixterm 最后能够制作一个独立版本,其中命令直接发送到 xterm

我在代码中主要遗漏的部分是 expect_background,它实际上在后台进行链接。希望它对所有想要自动化 xterm 的人有所帮助。所有学分归功于 Mr.Thomas Dickey 和 Mr.Don Libes!!!

#!/usr/bin/tclsh
package require Expect
set ::xtermStarted 0
set xtermCmd      $env(SHELL)
set xtermArgs     ""

# set up verbose mechanism early
set verbose 0
proc verbose {msg} {
    if {$::verbose} {
    if {[info level] > 1} {
        set proc [lindex [info level -1] 0]
    } else {
        set proc main
    }
        puts "$proc: $msg"
    }
}
# ::xtermSid is an array of xterm spawn ids indexed by process spawn ids.
# ::xtermPid is an array of xterm pids indexed by process spawn id.

######################################################################
# create an xterm and establish connections
######################################################################

proc xtermStart {cmd name} {
    verbose "starting new xterm running $cmd with name $name"
    ######################################################################
    # create pty for xterm
    ######################################################################
    set pid [spawn -noecho -pty]
    verbose "spawn -pty: pid = $pid, spawn_id = $spawn_id"
    set ::sidXterm $spawn_id
    stty raw -echo < $spawn_out(slave,name)
    regexp ".*(.)(.)" $spawn_out(slave,name) dummy c1 c2
    if {[string compare $c1 "/"] == 0} {
        set c1 0
    }
    ######################################################################
    # start new xterm
    ######################################################################
    set xtermpid [eval exec xterm -name dinesh -S$c1$c2$spawn_out(slave,fd) $::xtermArgs &]
    verbose "xterm: pid = $xtermpid"
    close -slave

    # xterm first sends back window id, save in environment so it can be
    # passed on to the new process
    log_user 0
    expect {
        eof {wait;return}
        -re (.*)\n {
            # convert hex to decimal
            # note quotes must be used here to avoid diagnostic from expr
            set ::env(WINDOWID) [expr "0x$expect_out(1,string)"]
        }
    }

    ######################################################################
    # start new process
    ######################################################################
    set pid [eval spawn -noecho $cmd]
    verbose "$cmd: pid = $pid, spawn_id = $spawn_id"
    set ::sidCmd $spawn_id

    ######################################################################
    # link everything back to spawn id of new process
    ######################################################################
   set ::xtermSid($::sidCmd) $::sidXterm
   set ::xtermPid($::sidCmd) $xtermpid

    ######################################################################
    # connect proc output to xterm output
    # connect xterm input to proc input
    ######################################################################
    expect_background {
        -i $::sidCmd
        -re ".+" {
            if {!$::xtermStarted} {set ::xtermStarted 1}
            sendTo $::sidXterm
        }
        eof [list xtermKill $::sidCmd]
        -i $::sidXterm
        -re ".+" {
            if {!$::xtermStarted} {set ::xtermStarted 1}
            sendTo $::sidCmd
        }
        eof [list xtermKill $::sidCmd]
    }
    vwait ::xtermStarted
}


######################################################################
# connect main window keystrokes to all xterms
######################################################################
proc xtermSend {A} {
    exp_send -raw -i $::sidCmd -- $A
}

proc sendTo {to} {
    exp_send -raw -i $to -- $::expect_out(buffer)
}


######################################################################
# clean up an individual process death or xterm death
######################################################################
proc xtermKill {s} {
    verbose "killing xterm $s"

    if {![info exists ::xtermPid($s)]} {
        verbose "too late, already dead"
        return
    }

    catch {exec /bin/kill -9 $::xtermPid($s)}
    unset ::xtermPid($s)

    # remove sid from activeList
    verbose "removing $s from active array"
    catch {unset ::activeArray($s)}

    verbose "removing from background handler $s"
    catch {expect_background -i $s}
    verbose "removing from background handler $::xtermSid($s)"
    catch {expect_background -i $::xtermSid($s)}
    verbose "closing proc"
    catch {close -i $s}
    verbose "closing xterm"
    catch {close -i $::xtermSid($s)}
    verbose "waiting on proc"
    wait -i $s
    wait -i $::xtermSid($s)
    verbose "done waiting"
    unset ::xtermSid($s)
    set ::forever NO
}

######################################################################
# create windows
######################################################################
# xtermKillAll is not intended to be user-callable.  It just kills
# the processes and that's it. A user-callable version would update
# the data structures, close the channels, etc.

proc xtermKillAll {} {
    foreach sid [array names ::xtermPid] {
        exec /bin/kill -9 $::xtermPid($sid)
    }
}

rename exit _exit
proc exit {{x 0}} {xtermKillAll;_exit $x}


xtermStart $xtermCmd $xtermCmd
xtermSend "ls -l\r"
xtermSend "pwd\r"
vwait ::forever