how to clean up the previous expect_buf in an expect script - tcl

I've written a expect function to get the output of a command and my code is like below
proc do_cmd {cmd id} {
set spawn_id $id
send "$cmd\r"
expect "$cmd\r"
expect {
-re "\n(.*)\r\n" {return $expect_out(1,string)}
default {exit 1}
}
}
If I call the function just once it would works fine and return something I want, but if I call it continually without a break, it would return something unwanted.
# test case 1
set ret [do_cmd $mycmd $spawn_id]
puts "$mycmd returns $ret" # the return value is ok
# test case 2
set ret [do_cmd $mycmd $spawn_id]
set ret [do_cmd $mycmd $spawn_id]
puts "$mycmd returns $ret" # the return value is not something I want
I use the 'exp_internal 1' to debug it and found that the expect_out in the second called command still holds the previous output info and caused the matched problem, so how can I clean up the expect_out buffer(I tried to set it an empty string but it doesn't work,) or is there anything else I can do to avoid this problem? Thanks in advance.

Don Libes's suggestion for your scenario is as follows,
Sometimes it is even useful to say:
expect *
Here the * matches anything. This is like saying, "I don't care what's
in the input buffer. Throw it away." This pattern always matches, even
if nothing is there. Remember that * matches anything, and the empty
string is anything! As a corollary of this behavior, this command
always returns immediately. It never waits for new data to arrive. It
does not have to since it matches everything.
Reference : Exploring Expect
In this case, after your required match, better try to save the match to some variable then simply add the code expect * at the last. This will empty the buffer. Your code can altered as below.
proc do_cmd {cmd id} {
set spawn_id $id
send "$cmd\r"
#Looks like you are looking for a particular command to arrive
expect "$cmd\r"
#Then you have one more expect here which is you want to get it
expect {
#Saving the value sub match to the variable 'result'
-re "\n(.*)\r\n" {set result $expect_out(1,string)}}
}
#Causing the buffer to clear and it will return quickly
expect *
return $result
}
Apart from this, there is one more way can be unsetting the expect_out(buffer) content itself which will remove the 'buffer' index from expect_out array which can be depicted as
unset expect_out(buffer)
When the next match happens, expect_out array will be updated the index 'buffer' and we can have the fresh expect_out(buffer) value. Replace the expect * with the above code if you prefer to use this way.
This is quite a workaround kind of stuff to get what we want actually. You can go ahead with any approach. Choice is yours. :)

Related

Tcl hang in proc return

I write 2 script to do somting like this:
#script1, to dump info:
proc script1 {} {
puts $file "set a 123"
puts $file "set b 456"
.....
}
(The file size I dump is 8GB)
#And use script2 to source it and do data category:
while { [get $file_wrtie_out_by_script1 line] != -1 } {
eval $line
}
close $file_wrtie_out_by_script1
Do the job....
return
In this case, the script is hang in return, how to solve the issue... stuck 3+ days, thnaks
Update:
Thanks for Colin, now I use source instead of eval, but even remove the "Do the job...", just keep return, still hang
The gets command will return the number of characters in the line that it just read from the file channel.
When all the lines of the file have been read, then gets will return -1.
Your problem is that you have a while loop that is never ending. Your while loop will terminate when gets returns 1. You need to change the condition to -1 for the while loop to terminate.
I agree with the comment from Colin that you should just use source instead of eval for each line. Using eval line-by-line will fail if you have a multi-line command (but that might not be the case in your example).

What is this line getting from a different file (TCL)?

I am including the relevant code below, and I can explain what I know it is doing up to this point:
proc rshm {where {i 0}} {
global ob
set what "???"
set ob(last_rshm_failed) "yes"
if {![info exists ob(shm)]} {
return "0.0"
}
if {[info exists ob(shm_puts_exist_in_progress)]} {
return "0.0"
}
shm_puts "g $where $i"
gets $ob(shm) istr
set what [lindex $istr 0]
set ob(last_rshm_failed) "no"
if {[string equal $what "?"]} {
set ob(last_rshm_failed) "yes"
puts stderr $istr
return "0.0"
}
set what [lindex $istr 3]
return $what
}
From looking at the rest of the program, I have concluded that the first two if statements are checking for errors elsewhere and are designed to terminate the procedure if the errors trigger.
Elsewhere in the program, the place (of interest) that the function gets called is in the form: rshm ft_xdev
Using print statements, I found that ft_xdev passes into the procedure as shm_puts "g ft_xdev 0".
The line that is throwing me off is the line: gets $ob(shm) istr
The call to $ob(shm) is another file (originally a binary program, but the readable version is in C...), but upon looking at this file, there is no reference to anything called "istr".
Would someone mind helping me out with what this line is getting from the other file? If needed, I can provide more code from the program.
The code:
gets $ob(shm) istr
will pass the contents of the ob(shm) variable (which should be a channel handle that is at least open for reading) and the string istr (which is used to name a variable in this case) into the gets command. The istr will be a local variable in this case because it hasn't been explicitly stated to be otherwise.
The gets command, when given two arguments, will read a line of text from the channel (first arg) and write that line of text to the variable (second arg). It then yields as result the number of characters read or -1 if there was a recoverable error condition such as end-of-file. You're ignoring the result. (Critical errors would become exceptions.) This is all documented on the manual page for gets.
tl;dr: Reads a line of text from the $ob(shm) channel and stores it in istr.
This procedure will return the 3rd index of $istr ([lindex $istr 3]) if its not empty or does not fail on the prior checks.
The contents of $istr are obtained from grabbing the next line of the open file channel $ob(shm) (if the channel is already open and $ob(shm_puts_exist_in_progress) does not exist).
If shm_puts "g $where $i" impacts the $ob in any way, it would be important to include the procedure shm_puts since it may impact $istr, but I suspect the contents of shm_puts are not relevant to $istr.
Finally if $istr starts with a ? then it aborts this procedure displaying the contents of $istr to stderr. That is, if a line of the file starts with ?, then the procedure aborts.
All procedure aborts (ie IF checks) do not retain the contents of $istr, since its a local variable not a global one, so checking the contents of $istr must be done within this procedure and after the gets command.

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.

if all else fails tcl script fails

I am trying to make a script to transfer file to another device. Since I cannot account for every error that may occur, I am trying to make an if-all-else fails situation:
spawn scp filename login#ip:filename
expect "word:"
send "password"
expect {
"100" {
puts "success"
} "\*" {
puts "Failed"
}
}
This always returns a Failed message and does not even transfer the file, where as this piece of code:
spawn scp filename login#ip:filename
expect "word:"
send "password"
expect "100"
puts "success"
shows the transfer of the file and prints a success message.
I cant understand what is wrong with my if-expect statement n the first piece of code.
The problem is because of \*. The backslash will be translated by Tcl, thereby making the \* into * alone which is then passed to expect as
expect *
As you know, * matches anything. This is like saying, "I don't care what's in the input buffer. Throw it away." This pattern always matches, even if nothing is there. Remember that * matches anything, and the empty string is anything! As a corollary of this behavior, this command always returns immediately. It never waits for new data to arrive. It does not have to since it matches everything.
I don't know why you have used *. Suppose, if your intention is to match literal asterisk sign, then use \\*.
The string \\* is translated by Tcl to \*. The pattern matcher then interprets the \* as a request to match a literal *.
expect "*" ;# matches * and? and X and abc
expect "\*" ;# matches * and? and X and abc
expect "\\*" ;# matches * but not? or X or abc
Just remember two rules:
Tcl translates backslash sequences.
The pattern matcher treats backs lashed characters as literals.
Note : Apart from question, one observation. You are referring your expect block as a if-else block. It is not same as If-Else block.
The reason is, in traditional if-else block, we know for sure that at least one of that block will be executed. But, in expect, it is not the case. It is more of like multiple if blocks alone.

How does this expect statement work in TCL?

I read the following code, but I do not understand how it works:
set accum ""
set timeout 1
expect {
-re {.+} {
set accum "${accum}$expect_out(0,string)"
exp_continue
}
}
set timeout 10
at the beginning, we set accum and timeout, then there is a expect command try to match something? and after it, we set the timeout as 10, how the whole code works? and does this mean?
Until the code times out (1 second after the last match of anything), any time it matches something (which is any sequence of characters — possibly excluding newline — because of -re {.+}) it appends it to the accum variable and restarts expecting something (the exp_continue is indeed magic).
It would be more efficient to use append accum $expect_out(0,string), but the way it is done isn't wrong.