using set and expect in shell script - bash

I am trying to ssh into few systems (read from test.txt file) using expect within a shell script and execute commands on each. The script returns an error "invalid command name". Am I using set and expect in an incorrect way here?
#!/usr/bin/expect -f
set username "root"
set pass "mypassword"
set fd [open /home/test.txt r]
set host [read $fd]
foreach line $host {
ssh -o StrictHostKeyChecking=no -n root#$host 'ls; pwd'
expect "User:" { send "${username}\r" }
expect "root's Password:" { send "${pass}\r" }
expect eof
}
Error returned
./expect.sh
spawn ssh -o StrictHostKeyChecking=no -n root#10.1.1.1
10.1.1.2
'ls
invalid command name "pwd'"
while executing
"pwd' "
("foreach" body line 3)
invoked from within
"foreach line $host {

As the error message suggests, expect parsed ; as a command separator, and couldn't handle pwd'.
That's because there are no single-quoted strings in the language.
Expect is tcl, you have to use double quotes: "ls; pwd"

Related

Inserting text through ssh without multiple password entries

I'm trying to setup a DNS server through ssh and am able to send text to the config files, but it requires a password for each line.
ssh -t $newDNS "sudo sed -i '4iforwarders { $IpDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^forwarders/i listen-on port 53 { $IpDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^listen-on/i allow-query { localhost; $subDNS; };' /etc/bind/named.conf.options"
ssh -t $newDNS "sudo sed -i '/^forwarders/a recursion yes; }/etc/bind/named.conf.options"
I think you either need to install a ssh key onto each server or use expect and type your password once.
The ssh key solution:
Read this link, but do not set up a password for your RSA key.
The expect solution:
#!/bin/bash
# This function is sent to the server. I've used it for testing
function dosomething {
ls
}
# The script expects a file with servers separated with newlines.
FILE=$1
# If the file does not exist, it exits.
[ -f "${FILE}" ] || exit 1
# I didn't know your password and username. If you would like me to add it,
# please sent me a DM or paste it as comment ;-)
read -sp "username: " USERNAME
echo
read -sp "password: " PASSWORD
echo
# While the content of ${FILE} isn't empty...
while IFS= read -r SERVER; do
# .. use expect to spawn ssh, login with ${PASSWORD}, send the dosomething to
# the server, execute dosomething and exit.
expect <<-EOF
spawn ssh ${USERNAME}#${SERVER}
expect "*: " { send "${PASSWORD}\r" }
expect "*$ " { send "$(typeset -f dosomething)\r" }
expect "*$ " { send "dosomething\r" }
expect "*$ " { send "exit\r" }
EOF
done < ${FILE}
# You're up ;-)
exit $?

Expect Script - enter password then run bash script

I have a bash script that calls an expect script with a password that initiates the ssh process:
#!/usr/bin/bash
/usr/bin/expect $PWD/sshScript.exp $SSHPASSWORD
The expect script calls the ssh command, waits for prompt to enter password and sends in the password.
#!/usr/bin/expect
set password [lindex $argv 0];
spawn ssh -o "StrictHostKeyChecking no" username#host
expect "Enter your AD Password:" {
send "$password\r"
}
I can get the remote server to ssh correctly and display the [user#host ~]$ but I want to add to the expect script a way to automatically run a bash script that is stored in the same location as the other two scripts.
I've tried to
a) scp the file to the remote and call it in the server but I can't seem to get expect to send any text pass the password
b) do spawn ssh -o "StrictHostKeyChecking no" username#host < secondscript.shto send in the script to run but it won't wait for the password to be entered before trying to run the script.
You may combine you bash and expect script together and use it to copy the 3rd bash script to the remote server, execute it and return the output.
Here's a simple NON-TESTED example:
#!/bin/bash
[... other bash code here ...]
SCRIPT='/path/to/script/to/run/remotely.sh'
LOGIN='test'
IP='your ip or hostname'
LOCATION='/destination/path/to/script/to/run/remotely.sh'
### start expect part here, you may add '-d' after 'expect' for debug
/usr/bin/expect << EOD
### set a 3 minute timeout
set timeout 180
### copy the script you wish to run on the remote machine
spawn scp -o StrictHostKeyChecking=no -p $SCRIPT $LOGIN#$IP:$LOCATION
expect {
timeout { send_user "\n# TIMED OUT. HOST NOT REACHABLE #\n"; exit 3 }
"*assword: "
}
send "your_password\r"
expect {
"*assword: " { send_user "\n# Incorrect Password. Login Failed. #\n"; exit 4 }
"100%" { send_user "\nFile copied\n" }
}
### ssh to remote server to run script
spawn ssh $LOGIN#$IP
expect {
timeout { send_user "\n# TIMED OUT. SSH DAEMON or PORT 22 CLOSED #\n"; exit 6 }
"*assword: "
}
send "your_password\r"
expect {
timeout { send_user "\n# TIMED OUT. PROMPT NOT RECOGNISED! #\n"; exit 7 }
### expect the prompt symbol
-re {[#>$] }
## execute your script on the remote machine
send "$LOCATION\r"
expect {
"enter what you expect here" { send_user "\nRESULT: Message based on what you set the expect to read $IP\n" }
-re {[#>$] }
}
[... other expect code here ... ]
### exit remote ssh session
send "exit\r"
### end of expect part of script
EOD
### continue bash script here
[... other bash code here ...]

Calling bash script that executes expect from within crontab

I have a bash script that dumps some databases and gz them. Then it is calling expect script that transfers the file to the backup server using scp (specific user created especially for this purpose). Here is a bash script:
The backupbases.sh file:
#!/bin/bash
today=$(date +"%Y-%m-%d")
dumpPath=/home/mbackup/
remotePass=thereispassword
for db in $(mysql -e 'show databases' -s --skip-column-names); do
dbname=$db
if [ $dbname == "somedatabasename" ]; then
fname=$today"-mr1a-"$db".gz"
if [ -x $dumpPath$fname ]; then
rm $dumpPath$fname
fi
mysqldump $db | gzip -c > $dumpPath$fname
expect transfer.sh mbackup#server_address:/home/mbackup/$fname $dumpPath$fname $remotePass
#rm $dumpPath$fname
fi
done
The tansfer.sh file:
#!/usr/bin/expect -f
# connect via scp
set remote [lindex $argv 0]
set localpath [lindex $argv 1]
set password [lindex $argv 2]
spawn scp $localpath "$remote"
#######################
expect {
-re ".*es.*o.*" {
exp_send "yes\r"
exp_continue
}
-re ".*sword.*" {
exp_send "$password\r"
}
}
expect eof
One thing that bothers me is that the passwords is ended with a # sign which could be interpreted as a comment but calling backupbases.sh in console results in successful execution of bash/expect. Unfortunately when crontab calls it - only the bash part is executed. Database is dumped but file is not transferred. Ofcourse the password and server address are provided.
How to resolve it?
Try to log the outputs of expect to a logfile so that perhaps you could know what's wrong with it. Also quote your arguments well to prevent splitting with spaces.
( expect transfer.sh "mbackup#server_address:/home/mbackup/$fname" "$dumpPath$fname" "$remotePass"; ) >/path/to/log/file 2>&1
By the way I recommend using a filename extension of .exp instead for expect scripts.

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
)
}

expect inside bash + wrong # args: should be "foreach varList list ?varList list ...? command"

I am using expect script inside bash script. When I use foreach inside expect script, it throws
wrong # args: should be "foreach varList list ?varList list ...? command"
The code:
#!/bin/bash
read -p "Enter username: " username
read -s -p "Enter password: " password
#Expect script
/bin/expect -<<EOD
set SERVERS {100 101 102}
foreach SERVER $SERVERS {
set timeout -1
spawn scp ${username}#plsa${SERVER}.corp.com:/log.2011-11-24 ${SERVER}
expect "*password:"; send "$password\r"
expect eof }
EOD
echo "completed"
Thanks
The heredoc (<<ENDTOK) is subject to shell expansion on the $variables. That means for each of the $variables you want expect to interpret, you'll need to escape the $.
The way to escape something is to prepend a slash to it ($ -> \$).
It appears the username and password are supposed to come from the shell, the rest from within expect, so here's how that should go:
#!/bin/bash
read -p "Enter username: " username
read -s -p "Enter password: " password
#Expect script
/bin/expect -<<EOD
set SERVERS {100 101 102}
foreach SERVER \$SERVERS {
set timeout -1
spawn scp ${username}#plsa\${SERVER}.corp.com:/log.2011-11-24 \${SERVER}
expect "*password:"; send "$password\r"
expect eof }
EOD
echo "completed"
Note the \ in front of $SERVERS and ${SERVER}.
You need to escape dollar signs with a backslash since $name is expended to the value of variable name:
/bin/expect -<<EOD
set SERVERS {100 101 102}
foreach SERVER \$SERVERS {
set timeout -1
spawn scp ${username}#plsa\${SERVER}.corp.com:/log.2011-11-24 \${SERVER}
expect "*password:"; send "$password\r"
expect eof }
EOD
If you quote your here-document delimiter, the embedded script is effectively quoted too:
/bin/expect -<<'EOD'
... expect script as posted ...
EOD

Resources