Expect spawn: No such file or directory error - bash

I am writing an Expect script that remotes to a server "server2" and executes fileexists.sh that will find if file exists at a particular location.
Bash script - fileexists.sh is as follows:
#!/bin/bash
if [ -s $1 ]; then
echo "non empty file exists."
else
echo "File doesnot exist or is empty."
Expect script - expec is as follows:
#!/usr/bin/expect
set username [lindex $argv 0]
set password [lindex $argv 1]
set hostname [lindex $argv 2]
set rfile [lindex $argv 3]
set prompt "$username* ~]$ "
spawn ssh -q -o StrictHostKeyChecking=no $username#$hostname
expect {
timeout {
puts "\n\nConnection timed out"
exit 1
}
"*assword:" {
send "$password\r"
}
}
expect {
"$prompt" {
puts "Logged in successfully.
}
}
spawn ./fileexists.sh $rfile
set lineterminationChar "\r"
expect {
$lineterminationChar { append output $expect_out(buffer);exp_continue}
eof { append output $expect_out(buffer)}
}
puts $output
When i call the expect script with ./expec user pass server2 /apps/bin/file.p I get output as "File doesnot exist or is empty." although the file exists at its location on server2.
i checked using expect:
if {[file exists $rfile]} { puts "file exists" }
And output i get is "file exists".
Seems to be something with spawn that I am unable to figure out.

To check on the remote host, do this (I assume you are logging in to a linux machine, or a machine with GNU stat): untested
send -- "stat -c '%s' '$rfile'\r"
expect {
-re {\r\n(\d+)\r\n} {
puts "$rfile exists with size $expect_out(1,string)"
}
-gl {*No such file or directory*} {
puts "$rfile does not exist"
}
}
send "exit\r"
expect eof

Related

Executing scp command from expect script - use of eof

I'm trying to create an expect script to perform and scp command to copy a given file to a remote machine.
If I run the following script, the scp command fails.
#!/usr/bin/expect -f
set ip_addr [lindex $argv 0]
set in_fname [lindex $argv 1]
set out_fname [lindex $argv 2]
set user [lindex $argv 3]
set password [lindex $argv 4]
set timeout 2
if {[llength $argv] != 5} {
send_user "Usage: ./scp_copy.sh <ip_address> <in_fname> <out_fname> <user> <password>\n"
exit 1
}
spawn scp $in_fname $user#$ip_addr:$out_fname
expect {
(yes/no) {send "yes\r"}
timeout
}
expect {
timeout
{
send_user "FAILED TO PERFORM SCP CMD.\n";
exit 1
}
password:
{
send "$password\r"
}
eof
}
... however, if I remove the 'eof' at the end of the second expect clause and instead create a new expect clause at the end, the scp command works.
expect eof
Please can someone explain this. I'm completely new to expect (and bash) scripts and would be grateful for a simple explanation that helps me understand.
What does 'expect eof' do exactly? Does eof indicate that the spawned process has complete? If so, should I introduce another timeout e.g.
expect {
timeout { exit }
eof
}
What is the difference between the eof being inside the second expect clause and it's own separate expect clause? I was expecting the same effect.
Do I need to call 'close' at the end of an expect script?
Thanks.

Why am I getting single quotes and unexpected behavior in my expect script?

I am trying to write a script that will sftp files to my amazon developer's account. Here is the script:
#!/usr/bin/expect --
#
#
set timeout -1
#log_user 1
if {[llength $argv] < 3 } {
puts "usage: sftp-to-amazon.exp <APPCODE> <APPNAME> <SFTP_USER>"
puts ""
puts "This script will sftp binary files to the amazon sftp server for the given APPCODE."
puts "and APPNAME. APPNAME is like Dragnet_AMZ_1951_V4."
puts "You can get the APPCODE from the Amazon Developers Console."
exit 1
}
set appcode [lindex $argv 0]
set appname [lindex $argv 1]
set sftp_user [lindex $argv 2]
puts "App code is $appcode app name is $appname sftp_user is $sftp_user"
stty -echo
send_user "Enter password for $sftp_user: "
expect_user -re "(.*)\n"
set sftp_pass $expect_out(1,string)
set sftp_host 'dar.amazon-digital-ftp.com'
puts "/usr/bin/sftp -o 'StrictHostKeyChecking no' ${sftp_user}#${sftp_host}"
if [ catch "spawn /usr/bin/sftp -o 'StrictHostKeyChecking no' $sftp_user#$sftp_host" reason ] {
puts "failed to spawn line 115 /usr/bin/sftp $sftp_user#$sftp_host : $reason\n"
set success 0
exit 1
}
expect -re "$sftp_user#$sftp_host's password: $" {
puts "Sending password"
send "$sftp_pass\r"
}
puts "Script complete."
When I run the script I get this output:
$ ./sftp-to-amazon.exp M1S3R61WOY9B0 ONETWO VM3H65THINGBATFA7
App code is M1S3R61WOY9B0 app name is ONETWO sftp_user is VM3H65THINGBATFA7
Enter password for VM3H65THINGBATFA7: /usr/bin/sftp -o 'StrictHostKeyChecking no' VM3H65THINGBATFA7#'dar.amazon-digital-ftp.com'
spawn /usr/bin/sftp -o 'StrictHostKeyChecking no' VM3H65THINGBATFA7#'dar.amazon-digital-ftp.com'
command-line: line 0: Bad configuration option: 'stricthostkeychecking
Couldn't read packet: Connection reset by peer
Script complete.
When I run ...
/usr/bin/sftp -o 'StrictHostKeyChecking no' VM3H65THINGBATFA7#'dar.amazon-digital-ftp.com'
... on its own from the command line it works fine.
Because single quotes have no special meaning in expect(Tcl).
if [ catch "spawn /usr/bin/sftp -o 'StrictHostKeyChecking no' $sftp_user#$sftp_host" reason ] {
# ^..................... ..^
# two separate words with literal quote chars
Tcl's equivalent of the shell's single quotes is curly braces. You need
if [ catch "spawn /usr/bin/sftp -o {StrictHostKeyChecking no} $sftp_user#$sftp_host" reason ] {
# ^........................^
# one word

Get the result of remote script in Expect

Im executing the remote script and checking return status of the script but if I do in the following way its returning the status of the password but not the status of the called script.How can I get the return status of the called script. Please help thanks in advance.
#!/usr/bin/expect
proc auto { } {
global argv
set timeout 120
set ip XXXX.XXX.XX.XX
set user name
set password pass
set ssh_opts {-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no}
set script /path-to-script/test.sh
spawn ssh {*}$ssh_opts $user#$ip bash $script {*}$argv
expect "Password:"
send "$password\r"
send "echo $?\r"
expect {
"0\r" { puts "Test passed."; }
timeout { puts "Test failed."; }
}
expect eof
}
auto {*}$argv
You're automating ssh bash remote_script, so you're not going to get a shell prompt where you can echo $? -- ssh will launch your script and then exit.
What you need to do is get the exit status of the spawned process (ssh is supposed to exit with the remote command's exit status). expect's wait command gets you the exit status (among other info)
spawn ssh {*}$ssh_opts $user#$ip bash $script {*}$argv
expect {
"Password:" { send "$password\r"; exp_continue }
timeout { puts "Test failed." }
eof
}
# ssh command is now finished
exp_close
set info [wait]
# [lindex $info 0] is the PID of the ssh process
# [lindex $info 1] is the spawn id
# [lindex $info 2] is the success/failure indicator
if {[lindex $info 2] == 0} {
puts "exit status = [lindex $info 3]"
} else {
puts "error code = [lindex $info 3]"
}

how can I tell expect to do something when it catches a string but not show it?

I'm using expect to send files via scp to different locations (branches of one company), because the WAN links are too slow I have to send the files to one server and after that execute a command (via ssh) in that server to retransmit the original files through the LAN there (to other systems). Now, the problem is that I use expect/ssh/scp on both scripts (the original one that does the initial connection, and the second one that sends the file through the LAN, both use ssh to do so). Both of them return the same error messages, so my (original) script exist because of errors found on the second script.
bash script to execute the scripts at the other end:
#!/bin/sh
for i in `cat banches.lst`
do
./execute-on-branch.exp $i "/bin/bash /tmp/retransmit.sh"
done
execute-on-branch.exp has:
#!/usr/bin/expect -f
set host [lindex $argv 0]
set dir [lindex $argv 1]
set timeout 30
set files [ glob $dir/* ]
set scp "scp $files root#$host:/tmp/$dir/"
spawn {*}$scp
set pass "blahblahblah"
expect {
"password: " {
send "$pass\r";
expect "Permission denied, please try again." {exit 22}
}
"Connection refused" {exit 111; exp_continue}
"Connection timed out" {exit 111; exp_continue}
"connection lost" {exit 111; exp_continue}
"lost connection" {exit 111; exp_continue}
"(yes/no)? " {send "yes\r" ; set timeout -1 ; exp_continue}
-re "." { exp_continue }
eof { exit }
}
interact
retransmit.sh has:
#!/bin/bash
for i in `cat /tmp/ips.lst`
do
/tmp/send.exp $i files/
done
send.exp has:
#!/usr/bin/expect -f
set dir [lindex $argv 0]
set host [lindex $argv 1]
set timeout 30
set files [ glob $dir/* ]
set scp "scp $files root#$host:/root/$dir/"
spawn {*}$scp
set pass "blahblahblah"
expect {
"password: " {
send "$pass\r";
expect "Permission denied, please try again." {exit 22}
}
"Connection refused" {exit 111}
"Connection timed out" {exit 111}
"connection lost" {exit 111}
"lost connection" {exit 111}
"(yes/no)? " {send "yes\r" ; set timeout -1 ; exp_continue}
-re "." { exp_continue }
eof { exit }
}
interact
If I get (at the retransmission) a "Conection refused" or "connection lost" the first expect script exists because it's supposed to act on the first servers not the one at the LAN, but it can tell the difference.
Is there a way to get the second expect to catch the "connection lost" but NOT print it so the first one doesn't see it?
thanks in advance!
dc//

bash + expect, running in background

I'm using expect to establish a persistent ssh connection
set stb_ip [lindex $argv 0]
spawn -noecho ssh -o ControlMaster=auto -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ConnectTimeout=1 -O exit root#$stb_ip
spawn -noecho ssh -fN -o ControlMaster=yes -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ControlPersist=360 -o ConnectTimeout=1 root#$stb_ip
expect {
-re ".*password:" {send "\r"; interact}
}
Unfortunately I can't manage to put this into background, I triend expect_background, fork+disconect but no luck.
Even triend running this from another script with
excpect -f script.ex param1 param2 &
but with no luck. Any help ?
Heres a proc you can use to login and then interact. I have not tried it with all the ssh Options but I don't see any reason it would not work. Since I use the 8.6 command "try" this is for 8.6 tcl only but you can modify the try to use catch for earlier versions pretty easily.
#!/bin/sh
# the next line restarts using wish \
exec /opt/usr8.6b.5/bin/tclsh8.6 "$0" ${1+"$#"}
if { [ catch {package require Expect } err ] != 0 } {
puts stderr "Unable to find package Expect ... adjust your auto_path!";
}
proc login { user password cmdline } {
set pid [spawn -noecho {*}$cmdline ]
set bad 0;
set done 0;
exp_internal 0; # set to one for extensive debug
log_user 0; # set to one to watch action
set timeout 10
set passwdcount 0
set errMsg {}
# regexp to match prompt after successfull login you may need to change
set intialpromptregexp {^.*[\$\#>]}
expect {
-i $spawn_id
-re $intialpromptregexp {
send_user $expect_out(0,string);
set done 1
}
-re {.*assword:} {
if { $passwdcount >= 1 } {
lappend errMsg "Invalid username or password for user $user"
set bad 1
} else {
exp_send -i $spawn_id "$password\r"
incr passwdcount
exp_continue;
}
}
-re {.*Host key verification failed.} {
lappend errMsg "Host key verification failed."
set bad 1
}
-re {.*onnection refused} {
lappend errMsg "Connection Refused"
set bad 1
}
-re {.*onnection closed by remote host} {
lappend errMsg "Connection Refused"
set bad 1
}
-re {.*Could not resolve hostname (.*): Name or service not known} {
lappend errMsg "Host invalid: Could not resolve hostname in $cmdline : Name or service not known"
set bad 1
}
-re {\(yes/no\)\?} {
exp_send -i $spawn_id "yes\r"
exp_continue;
}
timeout {
lappend errMsg "timeout \[[expr { [clock seconds] - $start } ]\]"
set bad 1
}
fullbuffer {
lappend errMsg " buffer is full"
exp_continue;
}
eof {
puts "Eof detected "
set bad 1
set done 1 ;
}
}
if { $bad } {
throw CONNECTION_ERROR [join $errMsg \n ]
}
return $spawn_id
}
# get login information in somehow in this case from command line
set user [lindex $argv 0]
set passwd [lindex $argv 1]
set host [lindex $argv 2 ]
try {
set spawn_id [login $user $passwd "ssh -X $user#$host" ]
} trap CONNECTION_ERROR a {
puts "CONNECTION ERROR: $a"
exit 1
}
interact
set exitstatus [ exp_wait -i $spawn_id ];
catch { exp_close -i $spawn_id };
# more clean up here if you want
Assuming your script works in the "foreground"...
nohup expect -f script.ex param1 param2 &
Here's a script I made a long time ago. It does what you want but doesn't use Expect (which I loathe). I don't use it any more, I can't guarantee that it even still works but it should get you going.
#!/bin/sh
#
# Persistent ssh: Automatically create persistent ssh connections using OpenSSH 4.0
[ -z "$USER" ] && USER=`whoami`
MASTERSOCKDIR="/tmp/pssh-$USER"
MASTERSOCK="$MASTERSOCKDIR/%r-%h-%p"
# Check if master is running
output=`ssh -o ControlPath="$MASTERSOCK" -O check "$#" 2>&1`
if [ $? -ne 0 ]; then
case "$output" in
Control*)
# Master not running, SSH supports master
# Figure out socket filename
socket=`echo "$output" | sed -n -e 's/[^(]*(\([^)]*\)).*/\1/p' -e '1q'`
# Clean old socket if valid filename
case "$socket" in
"$MASTERSOCKDIR"/*) rm -f "$socket" >/dev/null 2>&1 ;;
esac
# Start persistent master connection
if [ ! -d "$MASTERSOCKDIR" ]; then
mkdir "$MASTERSOCKDIR"
chmod 700 "$MASTERSOCKDIR"
fi
ssh -o ControlPath="$MASTERSOCK" -MNf "$#"
if [ $? -ne 0 ]; then
echo "$0: Can't create master SSH connection, falling back to regular SSH" >&2
fi
;;
*)
# SSH doesn't support master or bad command line parameters
ERRCODE=$?
echo "$output" >&2
echo "$0: SSH doesn't support persistent connections or bad parameters" >&2
exit $ERRCODE
;;
esac
fi
exec ssh -o ControlPath="$MASTERSOCK" -o ControlMaster=no "$#"
To execute an expect script in the background use expect eof at the end of your expect script. In case you have defined interact remove it from your script.
Changed script of OP
set stb_ip [lindex $argv 0]
spawn -noecho ssh -o ControlMaster=auto -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ConnectTimeout=1 -O exit root#$stb_ip
spawn -noecho ssh -fN -o ControlMaster=yes -o ControlPath=/tmp/ssh-master-%r#%h:%p -o ControlPersist=360 -o ConnectTimeout=1 root#$stb_ip
expect {
-re ".*password:" {send "\r"; interact}
}
expect eof
An other example [1].
#!/usr/bin/expect -f
set host "host"
set password "password"
spawn ssh $host
expect {
"(yes/no)?" {
send -- "yes\r"
exp_continue
}
"*password:*" {
send -- "$password\r"
}
}
##Removing this:
#interact
##And adding this:
expect eof
exit

Resources