I have the following code for listening at a serial port:
set timeout -1
log_user 0
set port [lindex $argv 0]
spawn /usr/bin/cu -l $port
proc receive { str } {
set timeout 5
expect
{
timeout { send_user "\nDone\n"; }
}
set timeout -1
}
expect {
"XXXXXX\r" { receive $expect_out(0,string); exp_continue; }
}
Why does this give a
invalid command name "
error after the 5 second timeout elapses in the procedure? Are the nested expects OK?
The problem is this:
expect
{
timeout { send_user "\nDone\n"; }
}
Newlines matter in Tcl scripts! When you use expect on its own, it just waits for the timeout (and processes any background expecting you've set up; none in this case). The next line, with what you're waiting for, is interpreted as a command all of its own with a very strange name (including newlines, spaces, etc.) which is not at all what you want.
What you actually want to do is this:
expect {
timeout { send_user "\nDone\n"; }
}
By putting the brace on the same line as the expect, you'll get the behaviour that you (presumably) anticipate.
Related
My code is running infinitely without coming out of loop.
I am calling expect script from shell script, that is working fine,
the problem here is script is not coming out of timout {} loop.
can someone help me in this regard.
spawn ssh ${USER}#${MACHINE}
set timeout 10
expect "Password: "
send -s "${PASS}\r"
expect $prompt
send "cmd\r"
expect $prompt
send "cmd1\r"
expect $prompt
send "cmd2\r"
expect $prompt
send "cmd3\r"
expect $prompt
send "cmdn\r"
#cmdn --> is about running script which takes around 4 hours
expect {
timeout { puts "Running ....." #<--- script is nout coming out of loop its running infinitely
exp_continue }
eof {puts "EOF occured"; exit 1}
"\$.*>" { puts "Finished.." ; exit 0}
}
The problem is that your real pattern, "\$.*>", is being matched literally and not as a regular expression. You need to pass the -re flag for that pattern to be matched as a RE, like this (I've used more lines than ; chars as I think it is clearer that way, but YMMV there):
expect {
timeout {
puts "Running ....."
exp_continue
}
eof {
puts "EOF occured"
exit 1
}
-re {\$.*>} {
puts "Finished.."
exit 0
}
}
It's also a really good idea to put regular expressions in {braces} if you can, so backslash sequences (and other Tcl metacharacters) inside don't get substituted. You don't have to… but 99.99% of all cases are better that way.
I have expect (tcl) script for automated task working properly - configuring network devices via telnet/ssh. Most of the cases there is 1,2 or 3 command lines to execute, BUT now I have more then 100 command lines to send via expect. How can I achieved this in smart and good scripting way :)
Because I can join all command lines over 100 to a variable "commandAll" with "\n" and "send" them one after another, but I think it's pretty ugly :) Is there a way without stacking them together to be readable in code or external file ?
#!/usr/bin/expect -f
set timeout 20
set ip_address "[lrange $argv 0 0]"
set hostname "[lrange $argv 1 1]"
set is_ok ""
# Commands
set command1 "configure snmp info 1"
set command2 "configure ntp info 2"
set command3 "configure cdp info 3"
#... more then 100 dif commands like this !
#... more then 100 dif commands like this !
#... more then 100 dif commands like this !
spawn telnet $ip_address
# login & Password & Get enable prompt
#-- snnipped--#
# Commands execution
# command1
expect "$enableprompt" { send "$command1\r# endCmd1\r" ; set is_ok "command1" }
if {$is_ok != "command1"} {
send_user "\n### 9 Exit before executing command1\n" ; exit
}
# command2
expect "#endCmd1" { send "$command2\r# endCmd2\r" ; set is_ok "command2" }
if {$is_ok != "command2"} {
send_user "\n### 9 Exit before executing command2\n" ; exit
}
# command3
expect "#endCmd2" { send "$command3\r\r\r# endCmd3\r" ; set is_ok "command3" }
if {$is_ok != "command3"} {
send_user "\n### 9 Exit before executing command3\n" ; exit
}
p.s. I'm using one approach for cheeking is given cmd line is executed successfully but I'm not certain that is perfect way :D
don't use numbered variables, use a list
set commands {
"configure snmp info 1"
"configure ntp info 2"
"configure cdp info 3"
...
}
If the commands are already in a file, you can read them into a list:
set fh [open commands.file]
set commands [split [read $fh] \n]
close $fh
Then, iterate over them:
expect $prompt
set n 0
foreach cmd $commands {
send "$cmd\r"
expect {
"some error string" {
send_user "command failed: ($n) $cmd"
exit 1
}
timeout {
send_user "command timed out: ($n) $cmd"
exit 1
}
$prompt
}
incr n
}
While yes, you can send long sequences of commands that way, it's usually a bad idea as it makes the overall script very brittle; if anything unexpected happens, the script just keeps on forcing the rest of the script over. Instead, it is better to have a sequence of sends interspersed with expects to check that what you've sent has been accepted. The only real case for sending a very long string over is when you're creating a function or file on the other side that will act as a subprogram that you call; in that case, there's no really meaningful place to stop and check for a prompt half way. But that's the exception.
Note that you can expect two things at once; that's often very helpful as it lets you check for errors directly. I mention this because it is a technique often neglected, yet it allows you to make your script far more robust.
...
send "please run step 41\r"
expect {
-re {error: (.*)} {
puts stderr "a problem happened: $expect_out(1,string)"
exit 1
}
"# " {
# Got the prompt; continue with the next step below
}
}
send "please run step 42\n"
...
If the sent ssh command times out, i need it to move to next address in list
It gets to where I send the pw, Stuff, and I need it to break out of that if it doesn't
get in. It just hangs. Why?
foreach address $perAddress {
set timeout 10
send "ssh $address user someone\r"
expect "word:"
send "Stuff\r"
expect {
"prompt*#" {continue}
timeout {break}
}
set perLine [split $fileData "\n"]
set timeout 600
foreach line $perLine {
send "$line\r"
expect "# "
}
send "exit\r"
expect "> "
}
The expect command swallows break and continue conditions (as it thinks of itself internally as a loop). This means that you'd need to do:
set timedOut 0
expect {
"prompt*#" {
# Do nothing here
}
timeout {
set timedOut 1
}
}
if {$timedOut} break
However, it is probably easier to just refactor that code so that the whole interaction with a particular address is in a procedure, and then use return:
proc talkToHost {address} {
global fileData
set timeout 10
send "ssh $address user someone\r"
expect "word:"
send "Stuff\r"
expect {
"prompt*#" {continue}
timeout {return}
}
set perLine [split $fileData "\n"]
set timeout 600
foreach line $perLine {
send "$line\r"
expect "# "
}
send "exit\r"
expect "> "
}
foreach address $perAddress {
talkToHost $address
}
I find it much easier to then focus on how to make things work correctly for one host independently of making them work across a whole load of them. (For example, you don't clean up the connection before going onto the next when there's a timeout; this leaks a virtual terminal until the overall script exits.)
I spawn a telnet process to a host. I send a command, expect something
in return. This goes on for a while. But somewhere in between this
interaction, the connection to the host is lost mysteriously and my
script dies while trying to "send" something to the spawned (now dead)
telnet process.
I'd like to write a procedure that takes the spawn id and the command
to be sent as arguments. I'd like to check if the spawn id exists
(i.e., the connection between the program and the host exists) before I
"send" the command. Otherwise, I'd like to exit.
Something like this:
proc Send {cmd sid} {
if { $sid is not dead yet } { ;## don't know how to do this
part
send -i $sid "$cmd\r"
} else {
puts "channel id: $sid does not exist anymore. Exiting"
exit
}
}
Rather than checking if the spawned process is still alive, you could catch the error that send raises when sending to a dead process:
proc Send {cmd sid} {
if {[catch {send -i $sid "$cmd\r"} err]} {
puts "error sending to $sid: $err"
exit
}
}
I ran into this problem before and used the Mac/Linux ps command to do that:
if {[catch {exec ps $pid} std_out] == 0} {
puts "Alive"
} else {
puts "It's dead, Jim"
}
If you are using Windows, I heard that the tlist.exe command does something similar, but I don't have a Windows machine to test it out.
I am a total expect noob.
I am writing a expect script for a test case where I want to count the number of occurrences of the string "Ok" and do an action for every occurrence from the following output:
Reloading configuration on all nodes
Reloading configuration on node 1 (node1-c5)
OK
Reloading configuration on node 2 (node2-c5)
OK
Reloading configuration on node 3 (node3-c5)
OK
Reloading configuration on node 4 (node4-c5)
OK
How would my expect block look like?
I'd rewrite your code to remove the while loop:
set number_ok 0
set node_patt "(Reloading configuration on node (\\d+?) \\((\[^)]+?)\\))\r\n(.+?)\r\n"
send "cluster config -r -a\r"
expect {
ERROR {cluster_exit 1}
-re $node_patt {
set line "<$cmdline> $expect_out(1,string)"
set node_num $expect_out(2,string)
set node_name $expect_out(3,string)
set result $expect_out(4,string)
switch -regexp $result {
"Node .+ not found" {
ok 0 "$line (not found)"
}
"Node .+ not responding, skipped" {
ok 0 "$line (not responding)"
}
OK {
ok 1 $line
incr number_ok
}
}
exp_continue ;# loop back to top of expect block
}
$ROOT_PROMPT ;# no action, so fall out of the expect block
}
Note that Tcl regular expressions are either entirely greedy or entirely non-greedy. I use \r\n(.+)\r\n to capture the line following "Reloading configuration on node ...". However the .+ part must not contain newlines, so it has to be non-greedy. Thus, every quantifier in node_patt has to be non-greedy.
The code ended up looking like this (a simple loop):
send "cluster config -r -a \r"
set done 0
set number_ok 0
while {$done == 0} {
set done 1
expect {
$ROOT_PROMPT { set done 1 }
"ERROR" { cluster_exit 1 }
-re "Reloading configuration on node.*\r" {
set line "<$cmdline> $expect_out(0,string)"
expect {
$ROOT_PROMPT { set done 1 }
"ERROR" { cluster_exit 1 }
-re "Node * not found" { ok 0 "$line (not found)" }
-re "Node * not responding, skipped" { ok 0 "$line (not responding)" }
"OK" {
ok 1 "$line"
set number_ok [expr $number_ok + 1]
set done 0
}
}
}
}
}
diag "Done $done"
diag "Count $number_ok"