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"
}
}
Related
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
I'm calling a function in which I'm scp files to remote server. but its giving out error.
Function below :-
scp_files()
{
######## scp files to remote host ###############
echo "**************************************************************************"
echo " scp files to remote host .. *"
echo "**************************************************************************"
echo "value of first argument is $1"
echo "silver username is $silver_username"
expect <<- DONE
log_user 0
set timeout -1
spawn scp commands.txt $silver_username#$1:/tmp/
expect "password: "
send "test123\r"
expect "$ "
send "exit\r"
expect eof
DONE
}
calling function
scp_files ${!REDIS_IP} ${!REDIS_PORT}
here im getting error as below :
send: spawn id exp5 not open
Where could be the issue here?
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.
This is a bash script needs to be run on Solaris which takes files from dirlocal, copies to backupdir and sends them via SFTP. The problem is it sends ony 4-5MB of data and then it hangs (somewhere at mput I think) while manually everything works fine. Don't know what to do really...
#!/bin/bash
export LD_LIBRARY_PATH=".:../tool/bin:$LD_LIBRARY_PATH"
dirlocal=</export/home/somedir>
dirlocal_backup=</export/home/somebackupdir>
filename=*
if [ ! -e ${dirlocal}/${filename} ]; then
exit
fi
hostname=<some_ip_address>
username=ftp
password=FTP
remotedir=<somedir>
cd ${dirlocal}
cp * ${dirlocal_backup}
../tool/bin/expect -f - <<EOFEXPECT1
set timeout 10
spawn -nottycopy -nottyinit sftp ${username}#${hostname}
expect {
(yes/no) {send yes\r ; exp_continue }
"*assword" {send ${password}\r ; exp_continue }
}
expect -timeout 10 sftp { }
send "cd ${remotedir}\r";
expect -timeout 10 sftp { }
send "mput ${filename}\r";
expect -timeout -1 sftp {send "bye\r" }
#send "exit\r"
expect eof
EOFEXPECT1
if [ $? -eq 0 ]
then
rm ${filename} > /dev/null 2>&1
else
echo "something"
fi
Since feature requests to mark a comment as an answer remain declined, I copy the above solution here.
Made it work finally. Edited expect statements were the clue. Thanks for help! Regards from Russia! – user2107698
I have a following script for executing commands on a remote device over ssh:
#!/usr/bin/expect -f
set cmd $argv
set timeout -1
spawn ssh -p22 USER#192.168.1.1
match_max 100000
expect "*?assword:*"
send "PASS\r"
expect "<*"
send $cmd\r
expect "* :"
send "Y\r"
expect feof
At the last line, my script is expecting "end of file" in order to exit. However the remote device never sends "end of file" even though the communication is over. Is there a possibility to exit on some sort of inactivity timer? Something like:
expect feof for 10 seconds
use eof not feof
set the timeout variable before the last expect:
send "Y\r"
set timeout 10
expect {
timeout {
send_user "no EOF after $timeout seconds"
exp_close
}
eof
}
wait