How to make expect timestamp logfile - bash

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)

Related

Loop in Except Script Variable Getting Value from Text File

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)

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

Expect script: to perform actions after closing ssh

The question is to preserve a variable and to perform actions after closing ssh within expect script inside bash.
This is what I`ve got so far:
echo "Getting package name..."
getPackageName=$(expect -c '
exp_internal 1
log_user 1
global expect_out
# puts "Getting package name..."
spawn ssh -q -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no -o RSAAuthentication=no -l user 10.20.30.40
sleep 1
expect {
"*sword*" {
send "12341234\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"*user#*>*" {
# getting name of the latest modified file
send "cd /export/home/user/Releases/build/1.3.32.0 && find * -type f -printf '"'"'%T# %p\\n'"'"' | sort -n | tail -1 | cut -f2- -d\" \"\r"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect {
"BUILD_MAIN*" {
# assigning value to variable
set result_lines [split $expect_out(0,string) \r\n]
set package_filename [lindex $result_lines 0]
puts "package_filename: $package_filename"
}
timeout {
send_user "Error: timeout\n"
exit 1
}
}
expect "*#"
send "exit\r"
# here I need to perform some actions on local machine after ssh logout
expect "Connection*"
send "export LATEST_BUILD=$package_filename\r"
send_user "Message sent to user"
')
So, in the bottom block I am trying to set environment variable (LATEST_BUILD) on the local machine after closing ssh, and also to paste there a value of variable (package_filename) which has been defined earlier during ssh session.
The point here is that I see the last "Message sent to user" in the output, but the previous send "export LATEST_BUILD=12345\r" obviously does not work.
#!/bin/bash
getPackageName=$(expect -c '
# A common prompt matcher
set prompt "%|>|#|\\\$ $"
# To suppress any other form of output generated by spawned process
log_user 0
### Spawning ssh here ###
spawn ssh user#xxx.xx.xxx.xxx
expect "password"
send "welcome!2E\r"
expect -re $prompt
# Your further code
send "exit\r"
expect eof
##### The below segment is not needed ######
##### if your intention is to get only the 'package_filename' value #####
# spawn bash
# expect -re $prompt
# send "export LATEST_BUILD=54.030\r"
# expect -re $prompt
# send "echo \$LATEST_BUILD\r"
# expect -re $prompt
# send "exit\r"
# expect eof
#
##### The End ######
# Enabling logging now ...
log_user 1
# Print only the value which you want to return
puts "$package_filename"
')
echo $getPackageName
eof is used to identify the end-of-file event i.e. closure of connection.
Note : The exported variable LATEST_BUILD only be available for the spawned bash session.
Update :
log_user is used to turn off/on the logging generated by Expect at any time.
log_user 0; # Turn off logging
log_user 1; # Turn on logging
I hope that your only intention is to get the package_filename. So, we don't even need to spawn bash shell. Instead, simply print the value at last, thereby making it to be available to the parent bash script.

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"

Mixing Expect and BASH

I have written a small Expect script to log into a Cisco device; once logged in I want to repeatedly run a command and grep the output.
#!/usr/bin/expect
send_user "Device name: "
expect_user -re "(.*)\n"
set host $expect_out(1,string)
send_user "Username: "
expect_user -re "(.*)\n"
set user $expect_out(1,string)
stty -echo
send_user -- "Password: "
expect_user -re "(.*)\n"
set pass $expect_out(1,string)
stty echo
send_user "show int "
expect_user -re "(.*)\n"
set intf $expect_out(1,string)
send_user "\n"
spawn telnet $host
expect "Username:"
send "$user\r"
expect "Password:"
send "$pass\r"
expect ">"
At this point we have logged into the device, I want to execute the command "show int xxx" repeatedly and grep the output for a specific line. grep isn't in Expect, nor a command like sleep, so I can loop round executing the show int command, grepping out my specific line. How can I mix Expect and Bash like this?
UPDATE: I've pretty much done the script now, I'll post the full script once I get over this last hurdle. A line set bytesnow [exec grep "packets input" \< showint | cut -d \ -f 9] is throwing the error;
child process exited abnormally
while executing
"exec grep "packets input" < \showint | cut -d \ -f 9"
But it works fine in a test script I wrote. The file ./showint is there, running that command on the command line works fine? I can't work out what's wrong?
UPDATE: More investigation (http://wiki.tcl.tk/8489) has shown me that the grep exits with status code 1, which means no pattern matches were found, put the command works just fine from the command line? Even with /full/path/to/showint.
END: I fixed my mistake by realising what a fool I had been, answered below. Thanks all for your help :D
This is what I would do
log_user 0
while(1) {
send -- "sh int $intf | i packets input\r"
set timeout 5
expect {
-re "^ +(\d+) packets" { send_user -- "$expect_out(1,string)" }
timeout { send_user "broke?\n" }
}
}
That'll get you the number of packets input.
This is my first Expect script, its purpose is to give the live (almost, 1 second!) throughput of an interface. The below example gives an interface input speed, because we grep for the line containing "packets input". Change this to "packets output" to get a live output rate for that interface.
#!/usr/bin/expect
# Long delay for those tricky hostnames
set timeout 60
# Prompt user for device name/IP, username, password,
# and interface to query (gi0/2)
send_user "Device name: "
expect_user -re "(.*)\n"
set host $expect_out(1,string)
send_user "Username: "
expect_user -re "(.*)\n"
set user $expect_out(1,string)
stty -echo
send_user "Password: "
expect_user -re "(.*)\n"
set pass $expect_out(1,string)
send_user "\n"
stty echo
send_user "show int "
expect_user -re "(.*)\n"
set intf $expect_out(1,string)
send_user "\n"
spawn telnet $host
expect "Username:"
send "$user\r"
expect "Password:"
send "$pass\r"
expect ">"
set byteslast 0
set bytesnow 0
log_user 0
# Enter a continuous loop grabbing the number of bytes that
# have passed through an interface, each second.
# The different in this number each cycle, is essentially
# how much traffic this interface is pushing.
while { true } {
send "show int $intf\r"
expect ">"
set showint [open "showint" "w"]
puts $showint $expect_out(buffer)
close $showint
set bytesnow [exec grep "packets input" \< showint | cut -d \ -f 9]
if { $bytesnow > $byteslast } {
set diff [expr $bytesnow - $byteslast]
set bps [exec expr "$diff" \* 8]
set kbps [exec expr "$bps" \/ 1000]
} elseif { $bytesnow < $byteslast } {
set diff [expr $byteslast - $bytesnow]
set bps [exec expr "$diff" \* 8]
set kbps [exec expr "$bps" \/ 1000]
} elseif { $bytesnow == $byteslast } {
set kbps 0
}
set byteslast $bytesnow
puts "$kbps Kbps\r"
sleep 1
}
As this is my first Expect script, I have no doubt it could be written more efficiently and clearly (that always the case I find), so if anyone has any pointers on this one I'm all ears! :)
My problem with my exec grep command turned out to be that prior to that, the file I had opened "showint", I hadn't closed, and I was trying to access another file; school boy mistake!

Resources