overly complex code problems - expect

So im trying to write a script that connects automatically via ssh
I have three files.
First is text file (file.txt) with my login credentials (now its only one but later there will be a few):
user1 abcd
Second file (connect.sh) is bash file that reads credentials and passes them to expect file Looks like this:
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
count=0;
words[0]="";
for word in $line; do
words[$count]="$word"
count=$(($count + 1))
done
myDir="$(dirname "$0")"
"$myDir/find.sh" "${words[1]}"
done < "$1"
The third file (find.sh) is /expect file which looks like this:
#!/usr/bin/expect
set password [lindex $argv 0]
spawn ssh "test#host.com"
expect "Password:"
send "$password\r"
interact
However when I try to login it fails to deliver the password. As far as I checked the password is sent correctly to the expect script. Also I tried a set timeout function but it does not work either.

As msw points out, you can do
#!/usr/bin/expect
proc main {passfile} {
set fh [open $passfile r]
while {[gets $fh line] != -1} {
lassign [split $line] user pass
connect $user $pass
}
close $fh
}
proc connect {user password} {
spawn ssh $user#host.com
expect "Password:"
send "$password\r"
interact
}
main [lindex $argv 0]
Then invoke your expect script with the password file
./test.exp file.txt
BTW, ".sh" is not a great extension to use for an expect program.

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.

expect adding curly brackets to password containing special characters

I wanted to write up a small expect script and another bash script to save the effort of typing password in ssh connection.
Here goes the scripts:
// ssh.exp, the real workhorse
#!/usr/bin/expect -f
# usage: ./ssh.exp host user pass
set host [lrange $argv 0 0]
set user [lrange $argv 1 1]
set pass [lrange $argv 2 2]
spawn ssh $user#$host
match_max 100000
expect "*?assword:*"
send -- "$pass\r"
send -- "\r"
interact
// the bash script to call it
#!/bin/bash
host='my.host.com'
user='someuser'
pass='Qwerty389$'
./ssh.exp $host $user $pass
However, when the test scripts run, the ssh server always complains that the password is incorrect.
I tried escaping the dollar sign, like pass='Qwerty389\$', but to no avail.
Put the debug statement exp_internal 1 into the expect script, and it shows that the password sent is:
send: sending "{Qwerty389$}\r" to { exp6 } // without escaping $
send: sending "{Qwerty389\$}\r" to { exp6 } // escaping $ in password
Not sure why expect put the curly brackets around the password passed to it. I verified that if there is no dollar sign in the password, there would not be the brackets.
Any help?
The shell code needs to quote variables:
./ssh.exp "$host" "$user" "$pass"
The expect code should not treat lists like plain strings. Extract the arguments with
lassign $argv host user pass
or if your expect is too old to have lassign, do
foreach {host user pass} $argv break
or (less DRY)
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]

shell script to ssh remote machines and print the output of the top command

I want to write a shell script to do the following four things:
ssh a remote machine (say hosti)
print the machine name to a file (top_out)
print the first few lines of the output of the 'top' command to the same file as in step2
repeat 1-3 for an other machine
I tried this:
#! /bin/bash
for i in 1 2 3 4 5 6 7 8
do
echo "host$i" >> ~/mysh/top_out
ssh host$i "top -n1 -b | head -n 15>> ~/mysh/top_out"
echo "done"
done
The output file that I got had saved the top output for some machines (say like host5-8), but it was blank for the early machinessay like host1-4. If I tried without the line "echo "host$i" >> ~/mysh/top_out", I can get the top output for all the host1-8.
When you do
ssh host$i "top -n1 -b | head -n 15>> ~/mysh/top_out"
you’re writing the output to ~/mysh/top_out on the remote host, not the local machine. The remote host might not be using the same physical home directory as your local machine. If you have NFS or something sharing your home directory on some machines but not all, then you’d see the symptoms you described.
Try doing
ssh host$i "top -n1 -b | head -n 15" >> ~/mysh/top_out
instead, or to make things slightly cleaner, maybe even
#!/bin/bash
for i in $(seq 1 8); do
(echo "host$i"
ssh host$i "top -n1 -b | head -n 15") >> ~/mysh/top_out
echo "done host$i"
done
you can try an expect script to save the output of each hosts after it connects to it, you can also add more commands to it, p.s. : this assumes you have the same username and password for all hosts :
#/usr/bin/expect -f
#write your hosts on a new line inside a file and save it in your workging directory as:
#host 1
#host 2
#host 3
#user '' for password if it contains special chars
#pass arguments to the script as ./script $username '$password' $hosttxt
set user [lindex $argv 0]
set pass [lindex $argv 1]
#pass path to txt file with line separated hosts
set fhost [lindex $argv 2]
#set this to the path where you need to save the output e.g /home/user/output.txt
set wd "/home/$user/log.txt"
#open hosts file for parsing
set fhosts [open $fhost r]
exec clear
#set loguser 1
proc get_top {filename line user pass} {
spawn ssh -l $user $line
expect {
"$ " {}
"(yes/no)? " {
send "yes\r"
expect -re "assword:|assword: "
send "$pass\r"
}
-re "assword:|assword: " {
send "$pass\r"
}
default {
send_user "Login failed\n"
exit 1
}
}
expect "$ " {}
send "top -n1 -b | head -n 15\r"
expect -re "\r\n(.*)\r(.*)(\\\$ |# )" {
set outcome "$expect_out(1,string)\r"
send "\r"
}
puts $filename "$outcome\n\n-------\n"
}
while {[gets $fhosts line] !=-1} {
set filename [open $wd "a+"]
get_top $filename $line $user $pass
close $filename
}
Check if the hosts for which you are not getting the output is showing following error:
TERM environment variable not set.
If you are getting this error for some of the hosts you can try following command:
ssh user#host "screen -r; top" >> file_where_you_want_to_save_output

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"

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.

Resources