resetting timeout timer of expect, while output is received from a send command - expect

Is it possible to reset the timeout timer while the command issued with "send" is printing it's output?
The case is that I'd like to run in many servers a command like find / -exec head {} /;. I want the timer to reset while the command prints output. I don't like to set the timeout to hours...
Thanks!

With exp_continue, this can is possible.
set threshold 1
set timeout 60; # 1 min
send "find / -exec head {} / \r"
expect {
# Your prompt here
"#"; {puts "prompt matched. cmd completed"}
timeout {
# Checking for max of 10 mins
if {$threshold>10} {
puts "output took more time than threshold"
exit 1
}
incr threshold
puts "Still waiting for the prompt"
# The below command will reset the expect timer
# and will cause the same expect loop to run again
exp_contintue;
}
}

Related

How to make expect's expect command fail if the output isn't matched?

Given the very simple script script.expect
#!/usr/bin/expect
spawn bash
expect "#"
send "/bin/false; echo \"process returned with $?\"\r"
expect -exact "process returned with 0"
send -- "exit\r"
expect eof
I don't seem to be able how the script can not fail since /bin/false will cause the echo command to print process returned with 1, thus process returned with 0 can never be matched on the expect command. I expect expect script.expect to fail with return code 1 after expect -exact "process returned with 0".
#!/usr/bin/expect
spawn bash
expect "#"
send "/bin/true; echo \"process returned with $?\"\r"
expect -exact "process returned with 0" {
send -- "exit\r"
expect eof
exit 0
}
exit 1
Even if I change the logic of my "application" in order to be able to test it with a positive/logically negated flow the outcome is still unexplainable.
I worked through
How to make expect command in expect program script to wait for exact string matching
https://www.thegeekstuff.com/2010/10/expect-examples
https://unix.stackexchange.com/questions/66520/error-handling-in-expect
https://unix.stackexchange.com/questions/79310/expect-script-within-bash-exit-codes?rq=1
and have no clue why expect is behaving this way.
In your first script, the expect -exact... command is "succeeding" with a timeout. The default timeout is 10 seconds, and the default action on timeout is to do nothing. So the commands waits 10 seconds, matches timeout, and returns, so we continue with the next command.
You can explicitly match for timeout:
expect {
-exact "process returned with 0" {}
timeout { puts "timeout!"; exit 1 }
}
To avoid the wait to timeout, you can use a regexp that will match whether $? is 0 or 1 (or other numbers). If you put part of the regexp in a capture group (), you can then find it in built-in variable $expect_out(1,string):
expect -re {process returned with ([0-9]+)}
set returncode $expect_out(1,string)
puts "we got $returncode"
exit $returncode
Note, the regexp uses {} style quotes, because "" quotes dont allow you to use [] inside them.

Global timeout handler in expect?

I've got an expect script that looks a bit like this:
set timeout 15
spawn someprocess
expect "a line"
expect "another line"
expect "some other line"
Essentially, it's waiting until these lines appear. There are no actions to be taken.
I don't want to write the following for every line that I'm looking for:
expect {
"a line" {}
timeout { exit 1 }
}
I want expect to return a non-zero status code (i.e. in $?) if it times out at any point. How do I do this?
You can setup an expect_before line that is run "in parallel" with any later expect commands to test for timeout. Just add after your spawn command
expect_before timeout { exit 1 }
If you want to error out if the spawned process exits, you can combine them as follows:
expect_before {
timeout { exit 1 }
eof { exit 1 }
}

Set timeout for expect script if execution time is input based

I have an expect script, which executes remote shell script.Now the time taken to execute shell script is based on inputs. More input, more time, less input less time.How should I set my timeout value because if set timeout as -1, then it will exit only with eof, and in case script gets hanged, then we will have a hanged session.
After analyzing the output pattern of the ongoing script, I found "OK" coming for all the lines read from the input file.So, I used it as a progress indicator to reset my timeout counter.
Following is the fraction of code I wrote to resolve my problem :
send -- "bash scriptname.sh \r"
expect {
-re "OK" {
exp_continue
}
-re "Enter XYZ value:" {
send "0000\r"
}
timeout {
exit
}
eof {
exit
}
}

handling tcl expect application crash

Is it possible to spawn an application, send commands, expect results, but also 'respawn' the application in case it crashed and continue from the last point? I tried creating procedures for spawning, but I am not able to catch user shell prompt once the application gets closed
So it sounds like you are doing something like telneting or sshing
into a shell then running an application. The application dies or
hangs so the prompt does not return so expect does not return and so
you are stuck. You will need to use timeout to detect a hang and restart
your entire process ( including the spawn). Below is the boiler plate I start with when writing
expect scripts. The critical thing to help you resolve your problem
is to realize that spawn not only sets the spawn_id but also returns
the pid of the spawned process. You can use that pid to kill the
spawned process if you get an eof and/or a timeout. My boiler plate
kills the spawn process on timeout. The timeout does not bail from the
expect loop but waits for the eof before exiting. it also accumulates
the output of expect command so on timeout you may be able to see
where it dies. The boilerplate is in a proc called run. Spawn the process
and pass the pid and the spawn id . Reuse the boiler plate to define other
procs that will be the steps. put the procs in a script with a counter between
them as shown and repeat. The other answerer is correct that the unless the app
restarts where you left off you can need to start from scratch. If it does start from
where you left off. make the steps granular enough that you know what command
to repeat. and step to start from.
BoilerPlate
proc run { pid spawn_id buf } {
upvar $buf buffer; #buffer to accumulate output of expect
set bad 0;
set done 0;
exp_internal 0; # set to one for extensive debug
log_user 0; # set to one to watch action
expect {
-i $spawn_id
-re {} {
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
timeout {
send_user "timeout\n"
append buffer $expect_out(buffer); # accumultate expect output
exec kill -9 $pid
set bad 1
exp_continue;
}
fullbuffer {
send_user " buffer is full\n"
append buffer $expect_out(buffer); # accumultate expect output
exp_continue;
}
eof {
send_user "Eof detected\n"
append buffer $expect_out(buffer); # accumultate expect output
set done 1 ;
}
}
set exitstatus [ exp_wait -i $spawn_id ];
catch { exp_close -i $spawn_id };
if { $bad } {
if { $done } {
throw EXP_TIMEOUT "Application timeout"
}
throw BAD_ERROR "unexpected failure "
}
return $exitstatus
}
set count 0
set attempts 0 ; # try 4 times
while { $count == 0 && $attempts < 4 } {
set buff ""
set pid [spawn -noecho ssh user#host ]
try {
run $pid $::spawn_id buff
incr count
run2 $pid $::spawn_id buff
incr count
run3 $pid $::spawn_id buff
incr count
run4 $pid $::spawn_id buff
incr count
} trap EXP_TIMEOUT { a b } {
puts "$a $b"
puts " program failed at step $count"
} on error { a b } {
puts "$a $b"
puts " program failed at step $count"
} finally {
if { $count == 4 } {
puts "success"
} else {
set count 0
incr attempts
puts "$buff"
puts "restarting\n"
}
}
}

Script not logging to log file. Why?

I have an expect/Tcl script as part of my bash script that logs into a remote router. Now, for testing purposes I am trying to handle the issue of time-out's. My problem is that the expect/Tcl script is not logging to my log file, and when it does it is logging everything the SSH connection is printing to my prompt which is not what I want.
Here's my expect script:
/usr/bin/expect<<EOF
set timeout 5
set send_human {.1 .3 1 .05 2}
set myStamp [exec date +\[%d\/%m\/%Y\ \%T\]]
set log_file ~/mylogfile.log
spawn ssh -o "StrictHostKeyChecking no" "me\#$1"
expect {
"password: " { send -h "mypassword\r" }
"No route to host" { exit 1 }
timeout { send_log "\$myStamp Timed out to $1\n"]; exit 1 }
}
send -h "reboot in 1\r"
sleep 1
send -h "exit\r"
expect eof
EOF
Please bear in mind that this is part of a function within my bash script that is passed the router name, hence the argument $1.
Any ideas?
You want to use the log_file command, not set a log_file variable
log_file ~/mylogfile.log
Other notes:
Tcl has a very nice builtin command to handle time, don't need to call out to date:
set myStamp [clock format [clock seconds] -format {[%d/%m/%Y %T]}]
the # character is not special in Tcl/expect and does not need to be escaped:
spawn ssh -o "StrictHostKeyChecking no" "me#$1"
As noted, log_file logs a transcript of the session. Just to log specific messages, you can use plain Tcl:
/usr/bin/expect <<EOF
proc log_msg {msg {to_stdout no}} {
set log_line "[timestamp -format {[%Y-%m-%d %T]}] \$msg"
set fh [open ~/mylogfile.log a]
puts \$fh \$log_line
close \$fh
if {\$to_stdout} {puts \$log_line}
}
# ...
expect {
"No route to host" {
log_msg "No route to host" yes
exit 1
}
timeout { log_msg "Timed out to $1"]; exit 1 }
}
# ...
EOF
This opens and closes the log for each message, which adds a bit of overhead. If milliseconds are important, open the log in the global scope, and use the global variable holding the file hendle in the log_msg proc.

Resources