Conditions in Expect script causing it to hang - bash

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

Related

Interact not working from within Expect Script

I have the following expect script within a bash script and I'm unsure as to why the interact command is not working.
expect <<-EOS
#!/usr/bin/expect
set timeout $EXP_TIMEOUT
send_user "\n The timeout being used is $EXP_TIMEOUT \n"
send_user "\nLogging into remote host via SSH:\n"
spawn ssh -q -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"
}
"$prompt" {
send -- "/usr/seos/bin/sesu - $user\r"
expect "*assword*"
send -- "$secret\r"
expect "$prompt"
send -- "id\r"
expect "$prompt"
send -- "hostname -s\r"
interact
}
}
expect eof
EOS
Thank you all for your help!
interact can't let the user enter data via stdin because you are already redirecting stdin for the here document.
Instead, you can save your here document with all expansions to a variable, and then pass that to -c. Here's a simplified example:
script=$(cat << EOF
spawn vi
send "iHello $(hostname)"
interact
EOF
)
expect -c "$script"

Expect script: to perform actions after closing ssh

The question is to preserve a variable and to perform actions after closing ssh within expect script inside bash.
This is what I`ve got so far:
echo "Getting package name..."
getPackageName=$(expect -c '
exp_internal 1
log_user 1
global expect_out
# puts "Getting package name..."
spawn ssh -q -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no -o RSAAuthentication=no -l user 10.20.30.40
sleep 1
expect {
"*sword*" {
send "12341234\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"*user#*>*" {
# getting name of the latest modified file
send "cd /export/home/user/Releases/build/1.3.32.0 && find * -type f -printf '"'"'%T# %p\\n'"'"' | sort -n | tail -1 | cut -f2- -d\" \"\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"BUILD_MAIN*" {
# assigning value to variable
set result_lines [split $expect_out(0,string) \r\n]
set package_filename [lindex $result_lines 0]
puts "package_filename: $package_filename"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect "*#"
send "exit\r"
# here I need to perform some actions on local machine after ssh logout
expect "Connection*"
send "export LATEST_BUILD=$package_filename\r"
send_user "Message sent to user"
')
So, in the bottom block I am trying to set environment variable (LATEST_BUILD) on the local machine after closing ssh, and also to paste there a value of variable (package_filename) which has been defined earlier during ssh session.
The point here is that I see the last "Message sent to user" in the output, but the previous send "export LATEST_BUILD=12345\r" obviously does not work.
#!/bin/bash
getPackageName=$(expect -c '
# A common prompt matcher
set prompt "%|>|#|\\\$ $"
# To suppress any other form of output generated by spawned process
log_user 0
### Spawning ssh here ###
spawn ssh user#xxx.xx.xxx.xxx
expect "password"
send "welcome!2E\r"
expect -re $prompt
# Your further code
send "exit\r"
expect eof
##### The below segment is not needed ######
##### if your intention is to get only the 'package_filename' value #####
# spawn bash
# expect -re $prompt
# send "export LATEST_BUILD=54.030\r"
# expect -re $prompt
# send "echo \$LATEST_BUILD\r"
# expect -re $prompt
# send "exit\r"
# expect eof
#
##### The End ######
# Enabling logging now ...
log_user 1
# Print only the value which you want to return
puts "$package_filename"
')
echo $getPackageName
eof is used to identify the end-of-file event i.e. closure of connection.
Note : The exported variable LATEST_BUILD only be available for the spawned bash session.
Update :
log_user is used to turn off/on the logging generated by Expect at any time.
log_user 0; # Turn off logging
log_user 1; # Turn on logging
I hope that your only intention is to get the package_filename. So, we don't even need to spawn bash shell. Instead, simply print the value at last, thereby making it to be available to the parent bash script.

Expect conditionals don't seem to be working

Edit: Everything works perfectly without the conditional. Adding in the conditional breaks the script. I've tried conditionals in other simpler scripts and those have worked.
Edit2: Adding the full script as other instances of expect conditionals have worked
I'm trying to modify an autoexpect generated expect script that works when the build succeeds but doesn't handle the build failing.
After significant research, I can't figure out why my expect conditional isn't working
#!/usr/bin/expect -f
set force_conservative 0 ;# set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
set timeout -1
spawn $env(SHELL)
match_max 100000
expect "~\$ "
send -- "sudo su\r"
expect "/home/ubuntu# "
send -- "cd ../../opt/application/"
send -- "\r"
expect "/opt/application# "
send -- "./buildwar.sh \r"
expect {
"BUILD SUCCESSFUL\r" {
send -- "su appuser\r"
expect "/opt/application\$ "
send -- "../../home/ubuntu/shutdown.sh \r"
expect "\"outcome\" => \"success\"}"
send -- "exit\r"
expect "/opt/application# "
send -- "./deploywar.sh \r"
expect "BUILD SUCCESSFUL"
send -- "su appuser\r"
expect "/opt/application\$ "
send -- "../../home/ubuntu/startup.sh \r"
expect "Deployed \"application.war\""
send -- "exit\r"
send -- "exit\r"
send -- "exit\r"
exit 0
}
"BUILD FAILED\r" {
exit 1
}
}
You may need to run with expect debugging on (expect -d). You don't actually show what data you are expecting.
It works for me:
$ expect -c '
log_user 0
spawn sh -c {echo "BUILD SUCCESSFUL"}
expect {
"BUILD SUCCESSFUL\r" {
#various and sundry other code to run
exit 123
}
"BUILD FAILED\r" {
exit 56
}
}
'
$ echo $?
123
$ expect -c '
log_user 0
spawn sh -c {echo "BUILD FAILED"}
expect {
"BUILD SUCCESSFUL\r" {
#various and sundry other code to run
exit 123
}
"BUILD FAILED\r" {
exit 56
}
}
'
$ echo $?
56
The issue was that nesting expect statements doesn't seem to work, so instead of the posted code, the following works:
#!/usr/bin/expect -f
set force_conservative 0 ;# set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
set timeout -1
spawn $env(SHELL)
match_max 100000
expect "~\$ "
send -- "sudo su\r"
expect "/home/ubuntu# "
send -- "cd ../../opt/application/"
send -- "\r"
expect "/opt/application# "
send -- "./buildwar.sh \r"
expect {
"BUILD SUCCESSFUL\r" {
send -- "su appuser\r"
}
"BUILD FAILED\r" {
exit 1
}
}
expect "/opt/application\$ "
send -- "../../home/ubuntu/shutdown.sh \r"
expect "\"outcome\" => \"success\"}"
send -- "exit\r"
expect "/opt/application# "
send -- "./deploywar.sh \r"
expect "BUILD SUCCESSFUL"
send -- "su appuser\r"
expect "/opt/application\$ "
send -- "../../home/ubuntu/startup.sh \r"
expect "Deployed \"application.war\""
send -- "exit\r"
send -- "exit\r"
send -- "exit\r"
You need to match against \n - though it won't matter if you just omit it.

how to check the file on remote host is exsit with shell script

i want check some files on remote host are exsit with shell script,for my local machine and remote host are not be trusted with each other,so i use expect in my script,here are my code
expect << EOF
spawn ssh $src_user#$src_host "test -f $src_pub || echo CheckFalse "
expect {
"yes/no*" {
send "yes\n"
}
"$src_host's password:" {
send "$src_passwd\n"
}
eof { exit }
}
expect CheckFalse { exit 11 }
EOF
if [ $? -ne 11 ];then
echo "file is exsit!"
else
echo "file is not exsit!"
fi
Use ssh with a command (using -c).
ssh otherhost -c 'ls /path/ filename'
And parse the output as you wish
There are a few issues with your script
Using test will only check if the file doesn't exist, rather use ls which gives output in both conditions and is easier to work with in this case.
You should use exp_continue after sending the authenticity check and the password so the expect loop can continue from where it left of from the previous match.
Add checks for $src_pub and 'No such file' in your expect block to trap for both conditions as shown below:
Try below:
spawn ssh $src_user#$src_host "ls $src_pub"
expect {
"yes/no*" {
send "yes\n"
exp_continue
}
-re "(.*)assword:" {
send -- "$src_passwd\n"
}
$src_pub {
exit 0;
}
-re "(.*) No such" {
exit 1;
}
}

How to capture SFTP transfer if successful from expect

I was lurking on this site for quite awhile now because I am doing an SFTP in a expect/SH script. SSH keygen is not an option for us since we don't have access to the remote server so we're looking into using expect to provide password arg for SFTP.
Here is the script I am working on, and everything is working here except I want to capture or log to an output file if my transfer ("mput") completed successfully. Any advice on what code to put after the "mput" since if I add an expect_out(buffer) after, it is failing.
#!/bin/ksh
DIRROOT=/apps/gen/e2k/sys/bpp
COPYDIR=$DIRROOT/SENT
FILEHASH=TEST.SOME.FILE.*
if [ ! -f $COPYDIR/$FILEHASH ]; then
echo "No File"
fi
# New FTP credential from GIC
FTPSERV=**********
FTPUSER=**********
FTPPWD=**********
FTPDIR=/to-scs
/usr/local/bin/expect -f - <<EOFEXPECT1
#exp_internal 1
set timeout -1
set log [open "/dir/dir1/dir2/MIKETEST.txt" w]
spawn sftp -oPort=10022 $FTPUSER#$FTPSERV
expect "password:"
send "$FTPPWD\r";
expect "sftp> "
send "lcd $COPYDIR \r";
expect "sftp> "
send "cd /recipient \r";
expect "sftp> "
send "mput TEST.SOME.FILE.*\r";
put $log $expect_out(buffer)
close $log
expect "sftp> "
send "bye\r";
expect eof
EOFEXPECT1
if [ $? -eq 0 ]
then
echo "success"
else
echo "fail"
fi
You should be using puts instead of put and i wouldn't rely on $expect_out(buffer) for error checking. Rather use a nested expect statement to trap for common sftp scenarios/errors.
set timeout -1
send "mput TEST.SOME.FILE.*\r";
expect {
#Check for progress, note does not work with all versions of SFTP
#If a match is found restart expect loop
-re "\[0-9]*%" {
set percent $expect_out(0,string)
puts $logf "File transfer at $percent, continuing..."
exp_continue
}
#Check for common errors, by no means all of them
-re "Couldn't|(.*)disconnect|(.*)stalled" {
puts $logf "Unable to transfer file"
exit 1
}
#OK continue
"sftp>" {
puts $logf "File transfer completed"
}
}
Finally, i don't recommend using timeout of -1 (never) as this will lead to a stuck process sooner or later. Rather make us of a timeout value and trap for the possibility of a timeout to occur in the expect block.
set timeout 60
expect {
#Process timed out
timeout {
puts $logf "File transfer timed out"
}
}

Resources