bash + expect, running in background - bash

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

Related

Expect spawn: No such file or directory error

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

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

expect for ssh passwordless

i have a file that has ip addresses, locations of files and passwords. I cannot use ssh key authentification, that's why i'm limited to use expect inside a bash script. the file contain the information as follow, seperated by spaces:
ip location password
ip location password
etc
my script is :
VAR=$(expect -c "
set fid [open "file.conf" w]
set content [read $fid]
close $fid
set records [split $content "\n"]
foreach rec $records {
set fields [split $rec]
lassign $fields\ ip location password
puts "$ip"
puts "$location"
puts "$password"}
spawn ssh $ip tail -f $location > /home/log_$ip 2>/dev/null &
expect {
".*Are.*.*yes.*no.*" { send "yes\n" }
"*?assword:*" { send "$password\r" }
}
")
echo "$VAR"
when i run the script, it gives me this error :
wrong # args: should be "read channelId ?numChars?" or "read ?-nonewline? channelId"
while executing
"read "
invoked from within
"set content [read ]"
You need to enclose the expect body in single quotes, so the expect variables are not expanded by the shell before expect starts to execute.
Also, if you hit the "are...yes...no" prompt, then you need to use exp_continue so you can keep expecting the password prompt.
DON'T OPEN THE CONF FILE WITH "w" -- you will destroy the file contents. You are reading from it, so open it for reading
VAR=$(expect -c '
set fid [open "file.conf" r]
while {[gets $fid line] != -1} {
lassign [split $line] ip location password
puts "$ip"
puts "$location"
puts "$password"
spawn ssh $ip tail -n 20 $location > /home/log_$ip 2>/dev/null &
expect {
-re "Are.*yes.*no" { send "yes\r"; exp_continue }
-gl "*?assword:*" { send "$password\r" }
}
}
')
I'm not sure if this will work when you redirect spawn output: I'm worried that expect will the have nothing to work with. If this doesn't work, remove "> /home/log_$ip 2>/dev/null &" or use
spawn ssh $ip tail -n 20 $location 2>/dev/null | tee /home/log_$ip
For backgrounding, you probably have to do something like this (untested)
expect -c '...' > /home/log_$ip 2>&1 &
expect_pid=$!
# ...
# later
wait $expect_pid
VAR=$(< /home/log_$ip)
do something with "$VAR"

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}

Redirect expect stdin to spawn call

Is there a way to redirect stdin sent to expect such that it is fed to a spawn call within expect? In my example below I am embedding expect within a shell function to which I want to pipe another shell script via heredoc and supressing the output by capturing it to a shell variable.
psshstdin() {
local user=$1 pass=$2 hosts=$3
out=$(expect -c '
set timeout 15
spawn pssh -i -h '"$hosts"' -p 100 -l '"$user"' -A -o ./ -x-oStrictHostKeyChecking=no <EXPECT_STDIN_HERE
expect "assword:" { send '\""$pass\r\""' }
interact
'<<EOF
echo "hello"
echo "world"
EOF
)
}
SOLUTION: I had to post this here since I don't have enough reputation points to answer my own question so quickly.
I was able to resolve it by trying the same techniques applied in this issue. I didn't think that solution was applicable initially, but it was. The working code is shown below.
psshstdin() {
local user=$1 pass=$2 hosts=$3
out=$(expect -c '
set timeout 30
spawn pssh -I -h '"$hosts"' -p 100 -l '"$user"' -A -o ./ -x-oStrictHostKeyChecking=no
while {[gets stdin line] != -1} {
send "$line\n"
}
send \004
expect "assword:" { send '\""$pass\r\""' }
expect {
"END_TOKEN_OF_SCRIPT" {
exit 0
}
default {
exit 1
}
}'<&0)
}
I can call it with something like:
psshstdin myusername mypassword ssh_hosts_file<<EOF
echo "hello"
echo "world"
EOF
You can capture stdin to a variable with stdin=$(cat -)
Update: let expect collect the stdin:
untested, but perhaps:
psshstdin() {
local user=$1 pass=$2 hosts=$3
out=$(expect -c '
set stdin [read stdin]
puts "debug: sending the following stdin to pssh:\n$stdin\n--END--"
set timeout 15
spawn echo "$stdin" | pssh -i -h '"$hosts"' -p 100 -l '"$user"' -A -o ./ -x-oStrictHostKeyChecking=no
expect "assword:" { send "'"$pass"'\r" }
interact
'<<EOF
echo "hello"
echo "world"
EOF
)
}

Resources