Calling bash script that executes expect from within crontab - bash

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.

Related

Redirect out of a expect-spawned process to a log file

I have a script that spawns another process.
I want to redirect output of that process to a log file.
But >> doesnt work
#!/bin/bash
#!/usr/local/bin/expect -f
####echo $#
/usr/local/bin/expect <<EOD
set timeout 10
spawn $sec/add.zims.user ${1} "${2} ${3}" >> /home/arvind/logs/ADD.log
expect "e4234234's Password: " { send "${4}\n" }
expect "*'s New password: " { send "${5}\n" }
expect "Enter the new password again:" { send "${5}\n" }
EOD
'''
Yes, the spawn command does not process shell redirections (1)
You could either let the shell handle the redirection:
/usr/local/bin/expect <<EOD
# ...
spawn ... ;# with no redirection
# ...
EOD
Or, use the expect log_file and log_user commands:
log_file -a $env(HOME)/logs/ADD.log will send output into that log file (appending unless you specify the -noappend flag). This does not turn off regular output to stdout
log_user 0 will turn off the usual stdout output. This does not alter logging using log_file
So:
/usr/local/bin/expect <<EOD
set timeout 10
log_user 0
log_file -a /home/arvind/logs/ADD.log
spawn $sec/add.zims.user ${1} "${2} ${3}"
# ...
END
(1) -- the exec command does do redirections, but not exactly like the shell: see the exec manual

Expect script if a user exists before creating

I wrote an expect script to create a user in unix server. It basically connects via SSH to a server using my credential and su to root to do useradd and etc. (I understand there are other methods to accomplish the same but I am restricted with such settings and environment currently.)
set prompt "(%|#|>|\\\$ )"
set prompt [string trim $prompt]
spawn ssh -o StrictHostKeyChecking=no -l $my_user $hostname
expect "?assword: "
send "$my_pass\r"
expect -re $prompt
send "/usr/bin/su - \r"
expect "?assword: "
send "$root_pass\r"
expect -re $prompt
send "/usr/sbin/useradd -d /export/home/$user -m -s /bin/sh $user \r"
expect -re $prompt
send "/usr/bin/passwd $user \r"
expect "?assword:"
send "$new_pass\r"
expect "?assword:"
send "$new_pass\r"
send "exit\r"
expect -re $prompt
send "exit\r"
expect -re $prompt
However if I am stuck at adding a logic to check whether a user already exists in the system. If it were in bash, I would have added grep -c '^USER' /etc/passwd to check for the returned number. But I am unable to capture the return number from expect. There is so much information returned once I added:
send "egrep -c '^$user' /etc/passwd \r"
set output $expect_out(buffer)
Could someone tell me how to parse out all the output? I know it is a very simple task. It is probably a simple if ... then .. else but I am unable to produce anything useful in the past week.
Assuming your shell on the remote host is sh-based, and the remote system is linux:
set cmd [format {getent passwd %s >/dev/null 2>&1; [ "$?" -eq 2 ] && /usr/sbin/useradd -d /export/home/%s -m -s /bin/sh %s} $user $user $user]
send "$cmd\r"
I'm using format (known as sprintf in other languages) to ease quoting.
After spending another few hours studying tcl, this is working now.
I replace this block of code after I enter the root_pass.
send "\r"
expect -re $prompt
expect *;
send "egrep -c '^$user:' /etc/passwd \r"
expect -re $prompt
set output $expect_out(buffer);
set ans [ split $output \n ]
set var [lindex $ans 1]
if { $var >= 1 } {
puts "Found.\r"
send "exit\r"
expect eof
} else {
puts "Not found.\r"
send "/usr/sbin/useradd -d /export/home/$user -m -s /bin/sh $user \r"
.....
}

overly complex code problems

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.

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

How to use Expect inside a Bash script

This is the code snippet I am using in the following Bash script:
for user_input in `awk '{print}' testfile_$$.txt`
do
ipaddress=`echo $user_input | cut -d';' -f 1`
command="${config_mode}`echo $user_input | cut -d';' -f 2-`"
ping -w 1 $ipaddress 1> /dev/null 2> $ERR_LOG_FILE 1> $LOG_FILE
if [ $? -eq 0 ];then
ssh "$USERNAME#$ipaddress" "$command"
>> $LOG_FILE
fi
done
How do I use Expect to automate the SSH login in this script?
I am very new to Expect and started testing this (it failed):
#!/usr/bin/bash
set force_conservative 0 ;# Set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
#
set timeout -1
spawn ssh auto21#10.38.227.229 {uname -a; df -h}
match_max 100000
expect "*?assword: "
send -- "bar01\r"
expect eof
Do I need to write the Bash script all over again in an Expect script or can Expect be used inside a Bash script?
If it can be done:
Moreover, I need to get the Bash variables $command, $username, $password, and $ipaddress and use it in the Expect part.
What solution would you suggest?
Or can I create an Expect script and call it from the Bash script just for login, error handling, execution, and logfiles.
Well, you will need to run two separate scripts, a shell script that calls an Expect script:
#!/usr/bin/bash
set force_conservative 0 ;
Change the above to
#!/usr/bin/expect
set force_conservative 0 ;
Or alternatively in your shell script I am unsure about the format, but you can send expect -c with the command to execute:
expect -c "send \"hello\n\"" -c "expect \"#\""
expect -c "send \"hello\n\"; expect \"#\""
Actually, there is also one other alternative:
#!/bin/bash
echo "shell script"
/usr/bin/expect<<EOF
set force_conservative 0 ;# Set to 1 to force conservative mode even if
;# script wasn't run conservatively originally
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
#
set timeout -1
spawn ssh auto21#10.38.227.229 {uname -a; df -h}
match_max 100000
expect "*?assword: "
send -- "bar01\r"
expect eof
EOF

Resources