How to use Expect inside a Bash script - bash

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

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 exiting after expect{send} statement

when I run this script that I wrote to help installing AUR packages:
enter #!/bin/bash
#bash
function GO() {
pack="$1"
cower -ddf $pack
cd "/home/$USER/applications/$pack"
expect -c " set timeout -1
eval spawn makepkg -Ascfi --noconfirm
expect -nocase \"password for $USER:\" {send \"$pass\r\"}
interact;"
cd "../"
}
package="$1"
echo "I need your password for this, can I have it please?"
read -s pass
cd "/home/$USER/applications"
if [ "$package" == "update" ]
then
file="/home/$USER/applications/update.pkgs"
cower -u > file
while IFS= read -r line
do
package=$(echo $line | cut -d " " -f2)
GO $package
done <"$file"
else
GO $package
fi
echo "have a good day."
exit 0
sometimes interact just stoppes after it enters the password and it just echos "have a good day." and exits. am I doing something wrong? timeout is < 0, I have interact aftet the expect statement, anything I am missing?
The only thing I can see is that the password might have a quote in it. You might want to do this:
env _user="$USER" _pass="$pass" expect <<'END'
set timeout -1
spawn makepkg -Ascfi --noconfirm
expect -nocase "password for $env(_user):" {
send -- $env(_pass)
send "\r"
}
interact
END
No need to eval spawn here.
Using the quoted heredoc makes the code easier to read too.

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.

Bash Expect script, send command

I have the following script working. I would like the result of command send -- "/system identity print\r" executed saved to a file, but it is probably badly written. At the moment, I can only write the path to the file tmp.
#!/bin/bash
HOSTNAME="xx.xx.xx.xx"
PORT="22422"
USER="admin"
PASS="pass"
TMP=$(mktemp)
PATH="/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin"
# Create Expect script
cat > $TMP << EOF
#exp_internal 1 # Uncomment for debug
set timeout -1
spawn ssh -p$PORT $USER#$HOSTNAME
match_max 100000
expect -exact "password:"
send -- "$PASS\r"
expect " > "
$nazwa send -- "/system identity print\r"
expect " > "
send -- "quit\r"
expect eof
EOF
# Run Expect script
#cat $TMP # Uncomment for debug
expect -f $TMP
echo $TMP >> log.log
# remove expect script
rm $TMP
Since your are actually developing your script, uncomment exp_internal 1 to get Expect to help you.

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