Expect script - run code on exit - expect

Is it possible to run code when an expect script it terminated?
Given the following example;
#!/usr/bin/expect
while { true } {
puts "I am alive"
sleep 5
}
puts "I am dead"
This will continuously print "I am alive". When I press CTRL+C to kill the script, how can I call a function (or similar) to print "I am dead" on the way out?

This link explains how to handle SIGINT in Expect. This is what you want to do in your code:
#!/usr/bin/expect
proc sigint_handler {} {
puts "I am dead"
exit
}
trap sigint_handler SIGINT
while { true } {
puts "I am alive"
sleep 5
}

Related

Conditions in Expect script causing it to hang

I'm using an expect script (within a bash script) to ssh into a remote server and execute a script, the local server will know that the remote server's script is finished as it (remote) will echo "finished" at the end of execution.
However, the script can be in one of three locations, so I've designed the following expect script, although in the code example it successfully executes the remote server script - it's unable to detect the printed "finished" and consequently hangs.
Local Server Expect Script:
versionScriptLoc1="/path/to/script1"
versionScriptLoc2="/path/to/script2"
versionScriptLoc3="/path/to/script3"
expect <<-EOS |& tee logfile.${hostname}.log
#!/usr/bin/expect
set timeout $EXP_TIMEOUT
puts "\nLogging into remote host via SSH..\n"
spawn ssh -q -tt -o ConnectTimeout=$SSH_TIMEOUT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${hostname}
expect "*assword*"
send -- "$secret\r"
expect {
"*assword*" {
send \x03
puts "\nIncorrect Password\n"
exit 1
}
"$prompt" {
send -- "/usr/seos/bin/sesu - $user\r"
expect "*assword*"
send -- "$secret\r"
expect "$prompt"
send -- "${versionScriptLoc1}\r"
expect {
"finished" {
}
"No such file or directory" {
puts "Location 1 execution Failed"
send -- "${versionScriptLoc2}\r"
expect {
"No such file or directory" {
puts "Location 2 execution Failed"
send -- "${versionScriptLoc3}\r"
}
}
exp_continue
}
}
send -- "exit\r"
expect "$prompt"
send -- "exit\r"
}
}
expect eof
exit 0
EOS
I thought exp_continue would make the expect loop and look for "finished". In the above code, the script is located in location 2, it sucessfully triggers but even though "finished" is printed, it still hangs.
Any help in why it's hanging/ potentially a better design would be highly appreciated.
PS: I know I should be spawning the scripts instead of triggering them like a user, but I've tried in the past and it didn't work.
I'd recommend you don't write your expect code so deeply nested.
The problem is that, after running script2, you hit the exp_continue whether
you're successful or not. Too bad expect doesn't have an exp_break or a
goto.
So a bit of restructuring is needed. I'm using some boolean variables to
manage the control flow.
Since I've introduces some Tcl variables, it will make separating the shell
vars from the tcl vars more difficult. We'll turn the shell vars into
environment vars, and quote the heredoc:
# ...
export EXP_TIMEOUT SSH_TIMEOUT
export hostname user secret prompt
export versionScriptLoc1 versionScriptLoc2 versionScriptLoc3
expect <<'EOS' 2>&1 | tee "logfile.${hostname}.log"
set timeout $env(EXP_TIMEOUT)
puts "\nLogging into remote host via SSH..\n"
spawn ssh -q -tt -o ConnectTimeout=$env(SSH_TIMEOUT) -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $env(hostname)
expect "*assword*"
send -- "$env(secret)\r"
expect {
"*assword*" {
send \x03
puts "\nIncorrect Password\n"
exit 1
}
"$env(prompt)"
}
send -- "/usr/seos/bin/sesu - $env(user)\r"
expect "*assword*"
send -- "$env(secret)\r"
expect "$env(prompt)"
set run2 false
send -- "$env(versionScriptLoc1)\r"
expect {
"No such file or directory" {
puts "Location 1 execution Failed"
set run2 true
}
"finished"
}
set run3 false
if {$run2} {
send -- "$env(versionScriptLoc2)\r"
expect {
"No such file or directory" {
puts "Location 2 execution Failed"
set run3 true
}
"finished"
}
}
if {$run3} {
send -- "$env(versionScriptLoc2)\r"
expect {
"No such file or directory" {
puts "Location 3 execution Failed"
}
"finished"
}
}
send -- "exit\r"
expect "$env(prompt)"
send -- "exit\r"
expect eof
exit 0
EOS

Getting exit code of an expect script within same function

I have the following code in a function. Is there a way to get the exit code after the expect script is run, however do a command with the exit code within the same function? I know you can you "$?" after the function is executed, however I would like to handle the exit code accordingly within the function itself.
getVersion(){
cmd="command_with_options"
mcasPassword="sample"
expect <<-EOS |& tee ${hostname}.log
#!/usr/bin/expect
set timeout $EXP_TIMEOUT
spawn sh
expect "$prompt"
send -- "$cmd\r"
expect "*assword"
send -- "$mcasPassword\r"
expect {
"*rror*" {
puts "\nAn issue was faced\n"
exit 1
}
"$prompt" {
puts "\nSuccessfully reached\n"
}
}
expect eof
exit 0
EOS
echo "Error code is as follows: $?"
#The above line isn't capturing the exit code ^
}
Any help would be highly appreciated.
Thank you #KamilCuk for posting the answer to this question:
echo "Error code is as follows: ${PIPESTATUS[0]}"

use expect to check if command output contains a word (else error)

I want to check if there is an interface in the output of the command:
ip a
If there is not, then I want to stop the execution. I've tried this code:
#!/usr/bin/expect -f
spawn bash
set spi_bash $spawn_id
send "ip a\r"
expect {
-re "docker0" {puts "docker0 is there\n"; exp_continue }
timeout {puts "no docker0\n"; exit 1}
}
exit
but exp_continue continues to timeout case.
I'd suggest you don't need expect for this:
set output [exec ip a]
if {[string first docker0 $output] != -1} {
puts "no docker0"
}
But if this is part of a larger expect program, you can write
spawn bash
set spi_bash $spawn_id
# a regex matching your prompt: a dollar sign, a space, and end of input
# adjust as required
set prompt {\$ $}
send "ip a\r"
set has_docker false
expect {
-re "docker0" {set has_docker true; exp_continue}
-re $prompt
}
send "exit\r"
expect eof
if {$has_docker}
puts "docker0 is there"
} else {
puts "no docker0"
}
exit [expr {!$has_docker}]

Why doesn't this expect handle timeout or eof

Why does this expect script continually spawn the ssh command, never printing "going to sleep" or "out of sleep", and never sleeping?
My intention is to try ssh'ing, if it sees "password: " to exit the while loop (more code not seen here would include an interact.) If 3 seconds go by, or ssh dies before then, it should puts, sleep for 3 seconds, puts again, and try ssh again.
The hostname "doesntexist" is used to force a failure, such as Name or service not known.
#!/usr/bin/expect -f
set readyForPassword 0
while { $readyForPassword == 0 } {
spawn ssh nouser#doesntexist
expect -timeout 3 {
"password: " {
set readyForPassword 1
} timeout {
puts "going to sleep"
sleep 3
puts "out of sleep"
} eof {
puts "going to sleep"
sleep 3
puts "out of sleep"
}
}
}
When -timeout flag is used, it should be prior to the Expect's pattern, not on the actions.
By debugging, we can find that the pattern being taken by Expect with your existing code is,
expect: does "" (spawn_id exp6) match glob pattern "\n "password: " {\n set readyForPassword 1\n } timeout {\n puts "going to sleep"\n sleep 3\n puts "out of sleep"\n } eof {\n puts "going to sleep"\n sleep 3\n puts "out of sleep"\n }\n "? no
From the 76th page of the Exploring Expect book, we can see the below statements,
The initial open brace causes Tcl to continue scanning additional
lines to complete the command. Once the matching brace is found, all
of the patterns and actions between the outer braces are passed to
expect as arguments
What went wrong then ?
The -timeout is not an action, but just a flag. Expect assumed the following as the pattern
"password: " {
set readyForPassword 1
} timeout {
puts "going to sleep"
sleep 3
puts "out of sleep"
} eof {
puts "going to sleep"
sleep 3
puts "out of sleep"
}
Remember, Expect does not mandate the action, only the pattern i.e. It will act as if we like only pattern is given, but no action to be taken.
Simply put, you code is as equivalent as
expect "Hello"; # Either 'timeout' or pattern can be matched. But, no action at all
Your code should be re-arranged as,
#!/usr/bin/expect -d
set readyForPassword 0
while { $readyForPassword == 0 } {
spawn ssh nouser#doesntexist
expect {
-timeout 3 "password: " {set readyForPassword 1}
timeout {
puts "going to sleep in timeout"
sleep 3
puts "out of sleep in timeout"
} eof {
puts "going to sleep in eof"
sleep 3
puts "out of sleep in eof"
}
}
}

How to get expect to set a bash variable

I would like the following expect script to set a variable in the main bash script. In other words I would like to set $SSH_SUCCESS to a pass/fail string. I have tried using $expect_out and $::env(SSH_SUCCESS) but have been unsuccessful. How do I set a bash variable from expect?
expect << EOF
log_user 0
log_file $TEST_LOG
set timeout 5
spawn ssh root#$RADIO_IP
.....
....expect script, echoing the return of an SSH command...
send "echo\$?\n"
expect {
"0" {
send_user "SSH test: PASSED\r"
SSH_SUCCESS="PASSED"
}
"1" {
send_user "SSH test: FAILED\r"
SSH_SUCCESS="FAILED"
}
sleep 1
send_user "\n"
exit
EOF
echo $SSH_SUCCESS
I don't know Expect, but I think it's something like this
SSH_SUCCESS=$(expect <<EOF
...
expect {
"0" {
puts "PASSED"
}
"1" {
puts "FAILED"
}
...
EOF
)
echo $SSH_SUCCESS
There is no way to set a variable.
This is another way to determine the exit status of Expect.
expect << EOF
log_user 0
log_file $TEST_LOG
set timeout 5
spawn ssh root#$RADIO_IP
.....
....expect script, echoing the return of an SSH command...
send "echo\$?\n"
expect -re "\[0-9\]+$"
#set latest command exit status to valiable "exit_code"
set exit_status \$expect_out(0,string)
if { \$exit_status == 0 } {
send_user "SSH test: PASSED\r"
} else {
send_user "SSH test: FAILED\r"
}
sleep 1
send_user "\n"
# exit expect as latest command exit status
exit \$exit_status
EOF
if [ $? -eq 0 ];then
SSH_SUCCESS="PASSED"
else
SSH_SUCCESS="FAILED"
fi
echo ${SSH_SUCCESS}

Resources