use expect to check if command output contains a word (else error) - expect

I want to check if there is an interface in the output of the command:
ip a
If there is not, then I want to stop the execution. I've tried this code:
#!/usr/bin/expect -f
spawn bash
set spi_bash $spawn_id
send "ip a\r"
expect {
-re "docker0" {puts "docker0 is there\n"; exp_continue }
timeout {puts "no docker0\n"; exit 1}
}
exit
but exp_continue continues to timeout case.

I'd suggest you don't need expect for this:
set output [exec ip a]
if {[string first docker0 $output] != -1} {
puts "no docker0"
}
But if this is part of a larger expect program, you can write
spawn bash
set spi_bash $spawn_id
# a regex matching your prompt: a dollar sign, a space, and end of input
# adjust as required
set prompt {\$ $}
send "ip a\r"
set has_docker false
expect {
-re "docker0" {set has_docker true; exp_continue}
-re $prompt
}
send "exit\r"
expect eof
if {$has_docker}
puts "docker0 is there"
} else {
puts "no docker0"
}
exit [expr {!$has_docker}]

Related

Expect script for scp 2 remotes does not copy all the files

I have 2 remote machines lets call them A and B.
I want to transfer files from A to B.
My expect script:
#!/usr/bin/expect
set cmd [lindex $argv 0]
set password1 [lindex $argv 1]
set password2 [lindex $argv 2]
spawn bash -c "$cmd"
expect {
-re ".*es.*o.*" {
exp_send "yes\r"
exp_continue
}
-re ".*sword.*" {
exp_send "$password1\r"
exp_continue
}
-re ".*sword.*" {
exp_send "$password2\r"
}
}
The expect script is used in a shell script:
expect $my_path/my_expect_script.exp "scp -r $remote_A_user#$remote_A_host:$file_path/* $remote_B_user#$remote_B_host:$file_path" $remote_A_password $remote_B_password
Always the script returns an error when entering password for the first time, in the same execution the next attempt works.
yes
qa#172.23.0.2's password:
Permission denied, please try again.
Sometimes some files are not copied to remote B.
Do you know how can I manage to execute a scp expect script on 2 remotes.
Thanks,
First, after you authenticate successfully, there are no more commands in the script, so expect exits and that kills scp. To wait for scp to finish, you have to wait for eof
Next, assuming the file transfer takes more than 10 seconds, you should set the timeout value to Infinity: set timeout -1
Last, the 2nd password will never be sent: the first password prompt will always match first. If those are meant to be two distinct password prompts, you need to make them match some unique text about the different hosts.
Also, indentation aids comprehension
spawn bash -c "$cmd"
set timeout -1
expect {
-re ".*es.*o.*" { # <== this is meaningless to the reader: add a whole word
exp_send "yes\r"
exp_continue
}
-re ".*sword.*" { # <== need something more here
exp_send "$password1\r"
exp_continue
}
-re ".*sword.*" { # <== need something more here
exp_send "$password2\r"
exp_continue
}
eof
}

How can I return an error in bash expect?

I have a bash expect script like this, that I use for some operations on Jamf:
spawn firmwarepasswd -setpasswd
expect {
"Enter password:" {
send "$oldpass\r"
exp_continue
}
"Enter new password:" {
send "$newpass\r"
exp_continue
}
"Re-enter new password:" {
send "$newpass\r"
exp_continue
}
}
If the password fails, the script will not exit and jamf will keep trying to execute it. How can I get it to return and exit when the password is wrong?
I don't know Jamf, but I do have a little example for you:
function _cmd {
local cmd="${#?No command?}"
echo -ne "Testing $cmd\t: "
expect 2>&1 <<-EOF
set timeout -1
spawn ${cmd}
expect eof
catch wait result
exit [lindex \$result 3]
EOF
echo $?
}
function _ssh {
local status="${#?No command?}"
read -sp "remote password? " remote_pass
echo -ne "\nTesting ssh\t: "
expect 2>&1 <<-EOF
set timeout -1
spawn ssh $USER#127.0.0.1
expect {
"yes/no" { send "yes\r"; exp_continue }
"*password: " { send "${remote_pass}\r" }
}
expect "*#" { send "exit $status\r" }
expect eof
catch wait result
exit [lindex \$result 3]
EOF
echo $?
}
_cmd false
_cmd true
_ssh 3
exit 0
The last part after expect eof makes sure that the exit status is shared. The _ssh command will exit with status 3.

[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...
}

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.

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