How to handle tcl expect output - tcl

I am writing an expect script to telnet to the router ,do some config,expect some output.
If the required prompt is not available then it waits for it and gets time out.
So how do i have to handle this and print an error msg.
set timeout 30;
puts "Telnet to $IP 2361\n\n";
spawn telnet $IP 2361;
expect ">";
send "ACT-USER::$user_name:1::$password;";
expect ">";
How do i handle and print an error msg if the expected value is not received?

Dealing with timeouts nicely requires a slightly more complex use of expect:
expect {
">" {
# Got it; don't need to do anything here because we run the code after
}
timeout {
send_user "timed out, oh no!\n"
exit 1
}
}
# Now we put the rest of the script...
send "ACT-USER....blah"
# ...
Note that I'm surprised that your send doesn't end in \r (to simulate pressing the Return key).

Related

Different output when a tcl script is launched via tclsh and wish interpreters

I wrote a Tcl script to telnet a Linux server and perform some operations in it.
I send some command and expect * (expected everything to be captured in buffer).
Now I print the $expect_out(buffer) in a file.
Note: the command I send to the linux server invokes some sort of application in Linux (like a top command), but the output of that command is always the same if executed any number of times.
Double clicking the script file or wish script.tcl (using wish interpreter)
After completing the execution, I open the file and check for its contents. It has the contents of previous expect, i.e., a previous expect before expect * in the script, and some part of this expect (expect *). So the file content misses some text at the end.
Running the script from command prompt (using tclsh interpreter)
#> tclsh script.tcl
After completing the execution, I open the file and check for its contents. It has the contents of previous expect, i.e., a previous expect before expect * in script, and a all part of this expect (expect *).
In the above cases, if expect * is replaced with some text like -> expect "hello", then the script fails in wish interpreter and suceeds in tclsh interpreter.
Why is that? How can I fix it?
The script is as below
package require Expect
set DOWN2 \033OB
spawn telnet 172.19.10.10
after 30000
expect "login:"
send "admin\r"
expect "Password:"
after 1000
send "welcome123\r"
after 2000
expect {
timeout { exit 1 }
"MAIN"
}
expect ">"
after 3000
send "mgmt\r"
expect {
timeout { send_user "timed out, Bye "; exit 1 }
"Protocol"
}
#set fp [open textdoc.txt a+]
send "translation\r"
expect "prefix"
send "\r\r"
send "$DOWN2"
send "$DOWN2"
send "$DOWN2"
send "$DOWN2"
send "326"
send "\x16"
expect -re {limit No. : (\d+)}
set lim_val $expect_out(1,string)
set exp_out $expect_out(buffer)
set fp [open textdoc.txt a+]
puts $fp "total buffer is $exp_out"
close $fp
after 2000
exit
This code is successful with tclsh and fails for wish interpreter
fails at line >> set lim_val $expect_out(1,string)
But from the wireshark capture, I have found out that the text "limit No. : 2" is received even if run from wish.
If I try to print $expect_out(buffer) in file,
In tclsh, the contents stored in buffer is till the "limit No. : 2" is occurred
In wish, the contents stored in buffer ends even before the text "limit No. : 2" is occurred.

Proper Error handling EXPECT / TCL

What is the best way to prevent sending commands to a dead process?
Sometimes my session gets terminated when it's supposed to be open so I end up sending commands and getting the error:
send: spawn id exp4 not open
I was trying to do something like
if [catch send "test\r"] {
puts "send error!"
}
but it seems like the query is true every pass.
that's the simplest example, but I have more complex "send / expects" where I use capture groups etc, so putting a catch around every "send / expect" or creating a function doesn't seem that useful.
can you wrap a catch around the entire program? What is the proper way to catch errors like these?
There's a FAQ written by the Expect author that addresses this: http://expect.sourceforge.net/FAQ.html#q64
Seems like you want something like
expect_before {
eof { respawning_the_process }
}
I'm sure there's some wrinkles to be ironed out (like what to do when the process is supposed to end)
The problem with this:
if [catch send "test\r"] {
is two-fold:
you did not put braces around the condition, so it's not getting evaluated at the right time.
you did not provide the right arguments to the catch command
You would want to write:
if {[catch {send "test\r"} output] != 0} {
This can be abstracted into a proc
proc send {args} {
set status [catch [list exp_send {*}$args] output]
# error handling if $status is non-zero
}
"exp_send" is a builtin alias for the expect "send" command, so it's safe to override "send" with a proc, minimizing the amount of code changes you need.

Expect: exit/finish/close interact on returned output

I need to use expect in order to navigate a program menu and then allow user input. After the user finishes the input, I need to tell expect to take away user control if a specific string is returned by that program.
expect -c '
[login etc, navigate to the desired option]
expect "[string]"; interact
[user input] -> until here everything works
expect "[specific string returned by the PROGRAM, NOT user input]"
[take away control from the user / exit interact if the above specific string is returned] -> this doesn't work
expect "string"; send "[exit command]\r"' -> what should happen after
Also, I need to trap all possible signals because if one of them is used, the user can end up at shell cli with root access.
I've been trying to find an answer on this for hours but my attempts on producing a valid syntax within expect ended up in frustration as the documentation did not help me at all in this direction.
Here's an example for you. Expect will be controlling this shell script:
#!/bin/sh
PS3="your choice? "
select answer in foo bar baz quit; do
case $answer in
quit) echo "bye bye"; break;;
*) echo "you chose $answer";;
esac
done
echo "out of select loop: hit enter..."
read x
echo "exiting ..."
The expect program is:
#!/usr/bin/env expect
spawn sh test.sh
set keyword "out of select loop"
# signal handlers: ignore these (can't trap SIGKILL)
trap SIG_IGN {HUP INT TERM QUIT}
# interact until a certain pattern is seen
# in the output (-o) of the process
interact {
\003 {
# prevent the user sending SIGINT to the spawned process
puts "don't press Ctrl-C again"
}
-o $keyword {
send_user "you quit\n"
send_user $keyword
return # "return" exits the interact command
}
}
# do other stuff here, for example, hit enter to allow the
# shell script to terminate
expect -re {\.\.\.\s*$}
send_user "... hitting enter ...\n"
send -- \r
expect eof
The expect man page is rough going. You'll be much happier with the Exploring Expect book.

Curly brackets in Expect language "expect" command?

I am very new to Expect/TCL and just wanted to confirm that in the example below, the curly-braces just work as single-quotes in bash to group the body of the expect command? Example:
expect {
-re "(P|p)assword: " { send "$pwd\r" }
-re "Connection timed out" { puts "Timeout error"; exit 1 }
-re "Connection closed" { puts "Host error"; exit 1 }
timeout { puts "Timeout error"; exit 1 }
eof { puts "Connection error"; exit 1 }
}
In standard Tcl, that's exactly how braces work. (OK, lots of command implementations then use the quoted thing immediately, but everything works pretty much as you'd expect.)
However, you're looking at the body of an expect command. The outer braces (on the first and last line) are the ones that are guaranteed to work that way; the others are up to the expect command — implemented in C — to interpret as it chooses. The documentation states that if the contents of the braces are multiline, they're interpreted as a (Tcl) list, which makes them work in pretty much the way you're after. (As Tcl commands go, that's deeply strange, but Expect has worked that way for decades now so it isn't about to change.)

exp::winnt_debug parent namespace error

I get the error "can't set "::exp::winnt_debug": parent namespace doesn't exist" when I try to run my expect script using the C implementation of expect interpreter on Windows (expect543.dll).
However the same script works fine if I run it through the ActiveState command tclsh...
The statement "set ::exp::winnt_debug 1" in the script is the cause of the error.
Any idea what might be the reason and how to resolve it?
Please find the code below
package require Expect
set ::exp::winnt_debug 1
set prompt "R4#"
set more " --More--"
expect -timeout 10 "$prompt"
set output [open result.txt "w"]
set running 1
spawn plink -telnet "144.21.12.45" -P 2004
send "enable\r"
send "\r"
send "show running-config\r"
send "\r"
while { $running > 0 } {
expect {
"\n" { puts -nonewline $output "$expect_out(buffer)" }
"$more" {send " "}
"lines *-* " { send " " }
#"$prompt" { set running 0 }
eof { set running 0 }
timeout { set running 0 }
}
}
puts "output is .."
There may be several implementations of Expect for Windows (unlike the Unix version, which has been stable for ages) and it sounds like the details of how they are implemented internally varies quite a bit between them. That's not especially surprising. Furthermore, the variable ::exp::winnt_debug is absolutely internal to a particular implementation.
The immediate fix is to change the line with the error to this:
catch {set ::exp::winnt_debug 1}
Like that, if it fails, it fails silently and won't cause the rest of the program to not run. (Enabling debugging shouldn't make any difference to whether the code runs!)
More generally, either use the ActiveState build (and work out how to package things together in the right way, bearing mind that critical dependency) or stop referring to internal features of it. It's very bad form to be poking your fingers inside the implementation of a package, as nobody ever gave a commitment to support them.