Loop in Except Script Variable Getting Value from Text File - expect

I have the following script:
#!/usr/bin/expect -f
# Set variables
set hostname [lindex $argv 0]
set username [lindex $argv 3]
set password [lindex $argv 1]
set devname [lindex $argv 4]
set enablepassword [lindex $argv 2]
set conft 0
# Log results
log_file -a ~/scripts/ExpectScripts/9120CiscoAPs/results.log
# Announce which device we are working on and at what time
send_user "\n"
send_user ">>>>> Working on $hostname $devname # [exec date] <<<<<\n"
send_user "\n"
# Don't check keys
set timeout 30
spawn ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30 $username\#$hostname
expect "User:"
send "$username\n"
expect "Password:"
send "$password\n"
expect "*>"
set f [open "aprebootlist2.txt"]
set aps [split [read $f] "\n"]
close $f
foreach ap $aps {
send "config ap reset $ap\n"
expect {
"*n)" {send "y\n"}
"*invalid." {send "\r"}
}
expect eof
close
}
send "\r"
expect "*>"
send "logout\n"
expect "*(y/N)"
send "n\r"
exit
This should log into a cisco wireless controller and basically run the command "ap config reset apvariable" a bunch of times. I tried to create a loop getting the variable for ap name from a text file. The script logs into the controller, reads the first name in the file, than fails with the below error. What am I doing wrong?
Error is:
Cisco Controller) >send: spawn id exp6 not open
while executing
"send "config ap reset $ap\n""
("foreach" body line 2)
invoked from within
"foreach ap $aps {
send "config ap reset $ap\n"
expect {
"*n)" {send "y\n"}
"*invalid." {send "\r"}
}
expect eof
..."
(file "./rebootapcommands2.exp" line 29)

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.

Hitting a return at password prompt in expect script instead of sending password

I am trying to have an expect script inside a bash to login to a router, execute a command and store output in a text file.
#!/usr/bin/bash
FQDN=$1
LogFile=/tmp/Router_${FQDN}.txt
> $LogFile
expect -d <<EOF > $LogFile
set timeout 20
set FQDN [lindex $argv 0]
set Username "user"
set Password "***$$$"
spawn ssh $Username#$FQDN
expect "*assword:"
send "$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
expect eof
EOF
cat $LogFile
I am getting the below error message.
system personnel =\r\r\n= may provide the evidence of such monitoring to law enforcement officials. =\r\r\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==\r\r\npassword: "
send: sending "\n" to { exp6 }
expect: does "" (spawn_id exp6) match glob pattern "#"? no
password:
Enter old password:
Based on the error it appears that script is hitting the {return} key "\r" which is not to be sent at password prompt.
I don't have a return once i ssh. Not sure where i am going wrong.
This is my expect script which is working fine. Its only when i code this inside a bash script its failing.
#!/usr/bin/expect -f
set timeout 20
set FQDN [lindex $argv 0]
set Username "user"
set Password "***$$$"
spawn ssh -o "StrictHostKeyChecking no" $Username#$FQDN
expect "*assword: "
send "$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
-Abhi
In a here-doc, variables like $Username and $Password are being expanded by the shell, so they're not seen as literals for Expect to expand. Since those shell variables aren't set anywhere, they're being expanded to null strings. As a result, it's executing ssh #$FQDN and sending an empty password.
You need to escape the $ so that Expect can process them.
You also don't need the set FQDN line in the Expect script, since you're using the shell variable for that.
#!/usr/bin/bash
FQDN=$1
LogFile=/tmp/Router_${FQDN}.txt
> $LogFile
expect -d <<EOF > $LogFile
set timeout 20
set Username "user"
set Password "***$$$"
spawn ssh \$Username#$FQDN
expect "*assword:"
send "\$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
expect eof
EOF
cat $LogFile
Or you could set them as shell variables, just like FQDN.
#!/usr/bin/bash
FQDN=$1
Username=user
Password="***$$$"
LogFile=/tmp/Router_${FQDN}.txt
> $LogFile
expect -d <<EOF > $LogFile
set timeout 20
spawn ssh $Username#$FQDN
expect "*assword:"
send "$Password\r"
expect "#"
send "some command\r"
expect "#"
send "exit\r"
sleep 1
exit
expect eof
EOF
cat $LogFile

How to make expect timestamp logfile

I'm trying to make my expect script to create one log file with a timestamp and create another with timestamp next time it runs but can't find any info on how.
I have a file with a list of hosts that expect connects to and run a set of command that I want to log all events from in one file when it runs.
Host1
Host2
Host3
etc
I have manage to create log file with timestamp:
log_file -a ~/log/[exec date]_results.log
Also tried with:
log_file -a -noappend ~/log/[exec date]_results.log
But it creates a new one for every line of hosts:
Thu Mar 8 15:28:24 CET 2018_results.log
Thu Mar 8 15:28:25 CET 2018_results.log
Thu Mar 8 15:28:26 CET 2018_results.log
Thu Mar 8 15:28:27 CET 2018_results.log
Thu Mar 8 15:28:28 CET 2018_results.log
I found one solution.
Added this to my .sh
timestamp=`date +%y-%m-%d_%H:%M`
logdir=~/log/
# This will rotate and append date stamp...
logfile=$logdir/results.log
newlogfile=$logfile.$timestamp
cp $logfile $newlogfile
This works but will only add timestamp to the rotated file, the results.log file will be the newest one and and the rotated file will be stamped with date and time from when you run the script, so the timestamp of that file will be wrong.
Here are both .sh and .exp scripts if there is a solution to timestamp all the files with the correct date/time.
.sh:
#!/bin/bash
# Collect the current user's ssh password file to copy.
echo -n "Enter the telnet password for $(whoami) : "
read -s -e password
echo -ne '\n'
echo -n "Enter the command to run on device : "
read -e command
echo -ne '\n'
timestamp=`date +%y-%m-%d_%H:%M`
logdir=~/log/
# This will rotate and append date stamp...
logfile=$logdir/results.log
newlogfile=$logfile.$timestamp
cp $logfile $newlogfile
names=()
ips=()
n=0
while read name ip; do
[[ -z $ip ]] && continue
names[n]=$name
ips[n]=$ip
((++n))
done < hostlist
for ((i=0; i<n; ++i)); do
./script.exp ${names[i]} ${ips[i]} $device "$password" "$command"
done
.exp:
#!/usr/bin/expect -f
# Set variables
set hostname [lindex $argv 0]
set ipadd [lindex $argv 1]
set username $env(USER)
set password [lindex $argv 2]
set command [lindex $argv 3]
set timeout 20
# Log results
log_file -a ~/log/results.log
# Announce which device we are working on and at what time
send_user "\n"
send_user ">>>>> Working on $hostname # [exec date] <<<<<\n"
send_user "\n"
expect_after timeout {send_user "Timeout happened connecting to $hostname; So, exiting....";exit 0}
spawn telnet $hostname
expect "*sername:"
send "$username\n"
expect "*assword:"
send "$password\n"
expect "*#"
send "$command $ipadd\n"
expect "*#"
send "exit\n"
expect ":~\$"
exit
You can do all of this from within Expect:
#!/usr/bin/expect -f
set host_file "hostlist"
if {![file readable $host_file]} {
error "cannot read $host_file"
}
# get info from user
set me [exec whoami]
stty -echo
send_user "Enter the telnet password for $me : "
expect_user -re {(.*)\n}
set password $expect_out(1,string)
send_user "\n"
stty echo
send_user "Enter the command to run on device : "
expect_user -re {(.*)\n}
set command $expect_out(1,string)
set timestamp [timestamp -format %Y-%m-%d_%H:%M]
set logfile $env(HOME)/log/results_$timestamp.log
log_file -a $logfile
expect_after timeout {send_user "Timeout happened connecting to $hostname; So, exiting....";exit 0}
set fh [open $host_file r]
while {[gets $fh line] != -1} {
lassign [regexp -all -inline {\S+} $line] hostname ip
if {$hostname eq "" || $ip eq ""} continue
send_user "\n"
send_user ">>>>> Working on $hostname # [timestamp -format %c] <<<<<\n"
send_user "\n"
spawn telnet $hostname
expect "*sername:"
send "$me\r"
expect "*assword:"
send "$password\r"
expect "*#"
send "$command $ip\r"
expect "*#"
send "exit\r"
expect eof
}
To handle timeouts for user input, there are 2 strategies:
set the timeout variable to -1 to wait indefinitely:
set prev_timeout $timeout
set timeout -1
send_user "Enter the thing: "
expect_user -re "(.*)\n" ;# waits forever
set the_thing $expect_out(1,string)
set timeout $prev_timeout
use an infinite loop to provide feedback to the user
while 1 {
send_user "Enter the thing: "
expect_user {
-re "(.*)\n" {break}
timeout {send_error "timeout!\n"}
}
}
set the_thing $expect_out(1,string)

[Expect Script]Separate log files for each device

My code is generated for one log file output from multiple devices.
How could we separate each device for each log file output?
Here is my code:
#!/usr/bin/expect -f
#Slurp up the input file
set fp [open "ip.txt" r]
# To avoid empty lines, 'nonewline' flag is used
set file_data [read -nonewline $fp]
close $fp
set prompt ">"
log_file -noappend router_status.txt
foreach ip [split $file_data "\n"] {
puts "Router $ip Interface Status"
spawn telnet $ip
expect "Username:"
send "username\r"
expect "assword:"
send "password\r"
expect $prompt
# To avoid sending 'Enter' key on huge configurations
send "show interface description\r"
expect {
-ex "---(more" { send -- " "; exp_continue }
"*>" { send "exit\r" }
}
set timeout 3; # Reverting to default timeout
# Sending 'exit' at global level prompt will close the connection
expect eof
}
You can simply achieve it by changing the log_file
foreach ip [split $file_data "\n"] {
puts "Router $ip Interface Status"
spawn telnet $ip
# Altering 'log_file' for each ip
log_file -noappend router_${ip}_status.log
# Your further code here...
}

lines from a file as variable in expect script

Need some help creating a loop in expect script where the variable is extracted from lines in a file.My current expect script is like below;
#!/usr/bin/expect -f
set i [open "samplelist"]
set hosts [split [read $i] "\n"]
set timeout -1
foreach host $hosts {
spawn /usr/bin/ssh appadm#$host
expect "appadm#$host:~>"
send "su epos\r"
expect "Password:"
send "pa55w0rd\r"
expect "epos#$host:/home/appadm>"
send "grep playlist /appl/epos/bin/cron.epos\r"
expect "epos#$host:/home/appadm>"
send "exit\r"
expect "appadm#$host:~>"
send "exit\r"
}
expect eof
close
However when i ran this script it does not terminate correctly after reading the last line of the file
Final modified expect script after being guided by Glenn
#!/usr/bin/expect -f
set i [open "samplelist"]
set hosts [split [read -nonewline $i] "\n"]
set timeout -1
foreach host $hosts {
spawn /usr/bin/ssh appadm#$host
expect "appadm#$host:~>"
send "su epos\r"
expect "Password:"
send "p#ssw0rd\r"
expect "epos#$host:/home/appadm>"
send "grep playlist /appl/epos/bin/cron.epos\r"
expect "epos#$host:/home/appadm>"
send "exit\r"
expect "appadm#$host:~>"
send "exit\r"
expect eof
}
The scripts reads a list of hosts from a file, loop connects and run a command to the remote host until the end of the list.

Resources