Tcl/Tk : embed expect send script inside wish script - tcl

I have a wish script which will do spawn, expect and send and get the required data.
When I run the script as wish ex.tcl, its not working.
Please find the code below for more information.
#!/usr/bin/wish
package require Expect
proc openSession { targetHost } {
log_user 1
set user $::env(USER)
set password "tmp1234"
set timeout 60
set spawn_id ""
puts "Ssh to $user#$targetHost"
spawn ssh -o UserKnownHostsFile=/dev/null $user#$targetHost
match_max -i $spawn_id 99999
expect {
"\(yes\/no\)\? " { send "yes\r" ; exp_continue }
"password\:" { puts 2 ; send "$password\r" ; exp_continue }
"$ " { puts 3 ; send "\r" ; puts "Ssh Session ready" }
timeout { puts 4 ; set spawn_id "" ; puts "Timeout during ssh $targetHost" }
}
set timeout 10
return $spawn_id
}
proc closeSession { sshSess args } {
puts "Closing Ssh session..."
expect -i $sshSess "$ " { send -i $sshSess "exit\r" }
expect -i $sshSess "closed." { puts "Connection Closed" }
catch { exp_close -i $sshSess }
catch { exp_wait -i $sshSess }
return
}
set sessId [openSession testbng76]
grid [ttk::button .mybutton -text Mytext]
closeSession $sessId
Error seen:
Are you sure you want to continue connecting (yes/no)? Error in startup script: wrong # args: should be "send ?options? interpName arg ?arg ...?"
while executing
"send "yes\r" "
invoked from within
"expect {
"\(yes\/no\)\? " { send "yes\r" ; exp_continue }
"password\:" { puts 2 ; send "$password\r" ; exp_continue }
"$ " { p..."
(procedure "openSession" line 12)
invoked from within
"openSession testbng76"
invoked from within
"set sessId [openSession testbng76]"
(file "./log.tcl" line 36)
How can I spawn the remote session and get the required output using tk?

Tk also has a send command for talking to other Tk-enabled interpreters (it works in a totally different way to Expect) and the two interact. Or rather Expect backs off when it sees Tk, and doesn't create a send command.
Fortunately, you can use exp_send instead; it's another name for exactly the functionality you want, and Expect always creates it. For example:
"\(yes\/no\)\? " { exp_send "yes\r" ; exp_continue }

Related

Expect script failed with "spawn id not open" when calling "expect eof"

I am trying to call an expect script from bash and have it return exit code in case of e.g. Connection Timeout.
Basically the bash script is calling expect like this:
if [[ -f $1 ]]; then
do something
else
script.exp $device
# here I want to evaluate the exit code but it is always 0
fi
I found some previous questions about it and that the right way to catch exit codes is:
expect eof
catch wait result
exit [lindex \$result 3].
The expect script:
#!/usr/bin/expect
exp_internal 1
log_user 1
set timeout -1
set hostname [lindex $argv 0]
set prompt "sftp>";
if { $hostname == "blah" } {
set username "user1"
set password "pass1"
} else {
set username "user2"
set password "pass2"
}
spawn sftp $username#$hostname "-o ConnectTimeout=3"
expect {
"timeout" {
puts "A timeout occured"
exp_continue
}
"password:" {
send "$password\r"
exp_continue
}
"sftp>" {
send "cd cfg\n"
expect $prompt
send "get * /some/dir/$hostname\n"
expect $prompt
send "get running-config /some/dir/$hostname-config\n"
expect $prompt
send "exit\r";
exp_continue
}
}
expect eof
catch wait result
exit [lindex \$result 3]
When I just test the expect script to see what happens, I get this error:
expect: spawn id exp4 not open
while executing
"expect eof"
I've tried moving expect eof around but it does not change the behavior. It states that the spawned process id is not open, which I guess is correct as it has exited with Timed Out/Connection closed?
Remove exp_continue after send "exit\r", otherwise EOF will be consumed by the expect { ... } block and another expect eof would fail.
Change lindex \$result 3 to lindex $result 3 as in the comments.
(Incorporating the previous comments)
Add it into the previous expect command, but with no body:
expect {
timeout {...}
password: {...}
sftp> {...}
eof
}
wait result
if {[lindex $result 2] == 0} {
exit [lindex $result 3]
} else {
error "system error: errno [lindex $result 3]"
}

How to fix 'send: spawn id exp4 not open while executing "exp_send -s "$cmd\r"" ' error

My TCL file has code like below,
proc executeCmd {cmd {file ""}} {
set out ""
set output ""
set send_slow {20 0.1}
set adminFlag 0
exp_send -s "$cmd\r"
for {set i1 0} {$i1 < 12} {incr i} {
set intimeout 0
expect {
# other options to check 'hostname', 'more', 'press any to continue' regexes
# ...
-regexp {^(.*)Press any key to continue.*$} {
set output [cleanOutput $expect_out(buffer)]
if {[regexp -- {\w+} $file]} {
append out $output
flush $fo
flush $clf
} else {
append out $output
}
exp_sleep 0.1
exp_send -s " "
exp_continue
}
timeout {
#log_msg INFO "TIMED OUT...."
puts "TIMED OUT"
set intimeout 1
puts "Executing $cmd >>> waiting for response from $hostname"
}
}
if {$intimeout} {
exp_send -s " "
} else {
break
}
}
return $out
}
spawn $plinkLoc -telnet $routerIP -P $routerPort
set out [executeCmd "term width 0"]
After executing this TCL through command prompt I am facing error saying,
send: spawn id exp4 not open
while executing
"exp_send -s "$cmd\r""
(procedure "executeCmd" line 28)
invoked from within
"executeCmd "term width 0""
invoked from within
"set out [executeCmd "term width 0"]""
This line 28 in TCL code is 'set' statement which is prior to 'executeCmd' proc, have updated the file for query purpose.
You have to either pass spawn_id to the procedure or declare the spawn_id as global.
Add this line inside procedure
global spawn_id

How to login to switch and execute commands if the prompt is varying?

I have a expect script :
expect - "$#" << 'END_OF_FILE'
set username [lindex $argv 0]
set hosts [lindex $argv 1]
set Passwordfile [lindex $argv 2 ]
set Commands "[lindex $argv 3 ];\r"
set prompt [lindex $argv 4 ]
# log_user 0
#exp_internal 1
if { [llength $argv] != 5} {
puts "usage: username hostname password commands prompt"
exit 1
}
set force_conservative 0 ;# set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
#
#COMMENTS
#SendCommands function sends the all the commands passed to it.All the commands passed to it must be separated by a semicolon and put a \r at last
set pfile [ open "$Passwordfile" "r"]
proc SendCommands { Commands } {
global prompt log errlog
foreach element [split $Commands ";"] {
expect {
-re $prompt
{send -- "$element\r"}
}
}
}
set timeout 20
foreach host [ split $hosts "\;" ] {
spawn ssh -o "StrictHostKeyChecking no" "$username#$host"
match_max 1000000
set expect_out(buffer) {}
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*"
{
send -- "[read $pfile]\r"
seek $pfile 0 start
}
}
expect {
timeout { send_user "\nLogin incorrect\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
-re "$prompt"
{ send -- "\r" }
}
set timeout 60
close "$pfile"
SendCommands "$Commands"
}
END_OF_FILE
i can execute it like :
./scriptname username hostname passwordfile "commnmad1;commnad2;command3" "(.*#|.*>)$"
but if i change modes by executing enable command i will get password prompt instead of
the usual prompt (# or >). how can i make sure that the below code is executed if the command is enable or i get a password prompt.
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*"
{
send -- "[read $pfile]\r"
seek $pfile 0 start
}
}
You probably want
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*" {
send -- "[read -nonewline $pfile]\r"
seek $pfile 0 start
exp_continue
}
$prompt
}
I added the -nonewline option to the read command. When you send the password, exp_continue will keep you in this expect "loop" until one of the other conditions is met, including the prompt.

How to get the complete output from expect when the internal buffer size of expect_out(buffer) size exceeds?

I dont know whats happening but i am not getting the complete output from the remote command executed possibly because expects internal buffer is getting execceded.
proc SendCommands { Commands } {
global prompt log errlog
foreach element [split $Commands ";"] {
expect {
-re $prompt
{send -- "$element\r"}
}
set outcome "$expect_out(buffer)"
foreach line [split $outcome \n] {
foreach word [regexp -all -inline {\S+} $line] {
if {( [string index [string trimleft $line " "] 0 ] == "%")} {
puts "$outcome"
exit 1
}
}
}
puts "$outcome"
}
}
set timeout 30
foreach host [ split $hosts "\;" ] {
spawn ssh -o "StrictHostKeyChecking no" "$username#$host"
match_max 10000000
expect {
timeout { send_user "\nFailed to get password prompt\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
"*?assword:*"
{
send -- "$password\r"
}
}
expect {
timeout { send_user "\nLogin incorrect\n"; exit 1 }
eof { send_user "\nSSH failure for $host\n"; exit 1 }
-re "$prompt"
{ send -- "\r" }
}
set timeout 300
SendCommands "$Commands"
}
this is how i am executing it :
./sendcommand aehj SWEs-elmPCI-B-01.tellus comnet1 "terminal length 0;show int description" "(.*#)$"
i am getting the complete output only when i remove log user 0 but when i use the puts command in the fucnction sendcommands above i get about 90 percent of it with 10 percent
of the trailing data at the end is not shown.
one way i am thinking is to use negation of regex in expect but it doesn't seem to work.
expect {
-re ! $prompt
{puts $expect_outcome(buffer)}
}
EDIT :I get all the output once when its executed about 5 or 7 times
After a little search i came up with this and seems to work but let me know of any execptions or better answers :
I set match_max = 1000 then
expect {
-re $prompt
{send -- "$element\r"}
full_buffer {
append outcome $expect_out(buffer)
exp_continue
}
}
append outcome $expect_out(buffer)
puts $outcome
but still when i set match_max = 10000 or 100 it fails again

get the result of remote command in Expect

I have Expect script invoked from a bash script. The Expect script spawns
a ssh session and runs a remote command. I want to have a return code
of the remote command available in my shell script.
# !/bin/sh
expect -f remote.exp 192.168.1.100 user pass 10.10.10.1
# here I want to analyze exit status of remotely invoked command
The expect script is below, here is my ugly workaround:
#!/usr/bin/expect
set timeout 20
set box [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set target [lindex $argv 3]
spawn ssh -l $user $box
expect "*password:"
send "$password\r\n"
expect "*#"
send "ping -c1 $target\r\n"
expect "*#"
send "echo $?\r"
expect {
"0\r" { puts "Test passed."; }
timeout { puts "Test failed."; }
}
expect "*#"
PS. I read that expect_out may help me, but I failed to get it work. Any help
will be greatly appreciated! Thanks.
I have tried the following and it works on a Mac:
#!/usr/bin/expect
# Process command parameters
foreach {host user password target} $argv {break}
set command "ping -c 1 $target"
# Log in
spawn ssh -l $user $host
expect -nocase "password:"
send "$password\r"
# Send the command
expect "$ "
send "$command\r"
# Echo the return code, the key here is to use the format code=X instead
# just a single digit. This way, Expect can find it
expect "$ "
send "echo code=$?\r"
# Analyze the result
set testResult "Test failed."
expect {
"code=0" { set testResult "Test passed." }
}
puts "$testResult"
# Done, exit
expect "$ "
send exit
Note a few differences:
The shell prompt on my system is "$ "
I only send \r, not \r\n. I don't know if it matters
I exit the ssh session by sending "exit"