expect script reading empty line - ssh

I have a list of IPs that I am looping through to ssh into each of them and capture some logs. Currently, it will loop through all the IPs and do what I want, the issue occurs when it hits the last IP, after it is done with the last line, it attempts to spawn another empty line resulting in an error. (spawn ssh root#)
How can I prevent this error from happening?
myexpect.sh
set user user
set pass pass
set timeout 600
# Get the list of hosts, one per line #####
set f [open "/my/ip/list.txt"]
set hosts [split [read $f] "\n"]
close $f
# Iterate over the hosts
foreach host $hosts {
spawn ssh $user#$host
expect {
"connecting (yes/no)? " {send "yes\r"; exp_continue}
"assword: " {send "$pass\r"}
}
expect "# "
send "myscript.sh -x\r"
expect "# "
send "exit\r"
expect eof
}
myiplist.txt
172.17.255.255
172.17.255.254
...
error:
[root#172.17.255.255: ]# exit //last ip in the list
Connection to 172.17.255.255 closed.
spawn ssh root#
ssh: Could not resolve hostname : Name or service not known
expect: spawn id exp5 not open

Text files end with a newline
first line\n
...
last line\n
So when you read the whole file into a variable, then split on newlines, your list looks like this:
{first line} {...} {last line} {}
because there is an empty string after the last newline.
The idiomatic way to iterate over the lines of a file in Tcl/expect is this:
set f [open file r]
while {[gets $f host] != -1} {
do something with $host
}
close $f
Or, use the -nonewline option of the read command
set f [open file]
set hosts [split [read -nonewline $f] \n]
close $f
foreach host $hosts {...}

Related

Using Expect Script to read two columns from text file

I need to write a script on Expect to read a text file that contains two columns. Column one is the site name (dns) and the second is a router interface that will be used as the source-interface when I perform an FTP function from a Cisco router. Here is the content of the text file (myfile.txt):
site1 Gi0/0/0.44
site2 GigabitEthernet0/0
site3 GigabitEthernet0/0
site4 GigabitEthernet0/0/0
site5 GigabitEthernet0/0/0
Here is my code. It is only able to read the first column.
You will see a variable named "source-int", it has not been defined anywhere yet. I just put it in the script as a placeholder.
#!/usr/bin/expect
#
#
set workingdir cisco/rtr
puts stdout "Enter TACACS Username:"
gets stdin tacuserid
system stty -echo
puts stdout "Enter TACACS password:"
gets stdin tacpswd
puts stdout "\nEnter enable password:"
gets stdin enabpswd
system stty echo
#
set RTR [open "$workingdir/myfile.txt" r]
#
while {[gets $RTR dnsname] != -1} {
if {[ string range $dnsname 0 0 ] != "#"} {
send_user "The value of the router name is $dnsname\n"
set timeout 10
set count 0
log_file -a -noappend $workingdir/session_$dnsname\_$timestamp.log
send_log "### /START-SSH-SESSION/ IP: $dnsname # [exec date] ###\n"
spawn ssh -o StrictHostKeyChecking=no -l $tacuserid $dnsname
expect -ex "TACACS Password: "
send "$tacpswd\r"
expect ">"
send "enable\r"
expect "assword: "
send "$enabpswd\r"
expect "#"
send "config t\r"
expect "#"
send "no ip ftp passive\r"
expect "#"
send "ip ftp username anonymous\r"
expect "#"
send "ip ftp source-interface $souce-int\r"
expect "#"
send "ip ftp password cisco\r"
expect "#"
send "end\r"
expect "#"
send "copy ftp://10.10.10.1/out/customer/ACL.txt flash:\r"
expect "\?"
send "\r"
expect "#"
send "end\r"
expect "#"
send "exit\r"
send_log "\n"
send_log "### /END-SSH-SESSION/ IP: $dnsname # [exec date] ###\n"
log_file
sleep 5
}
}
exit
Any assistance is greatly appreciated.
Thanks!
You can use tcl's regexp command to split the line into 2 words.
if {[regexp {(\S+)\s+(\S+)} "$dnsname" dummy dns interface] == 1} {
puts "dns=$dns interface=$interface"
}
The regexp code \s matches a whitespace character, and \S a non-whitespace character. The + suffix matches 1 or more of the previous code. The () capture the wanted parts, and they are put by the command into the variables dns and interface.

Capture exit code of Expect script

I have an expect script that will transfer the file from one server to another. This expect script is being kicked off by the shell script. The expect script can transfer the file if it is available in source and it will give a zero exit code. However, when the file is not available in source, the script will not transfer the file but it will still give a zero exit code.
How can I capture the correct exit code of the expect script.
test.sh:
#!/bin/sh
DT=`date +%Y%m%d`
RXHOME=/tmp'
x_server=A1234
x_file="test_${DT}.dat"
cd $RXHOME/scripts
./test.exp ${x_server} ${x_file}
if [ $? -ne 0 ]
then
echo "ERROR : Expect script encountered an error."
exit 1
fi
test.exp:
#!/usr/local/bin/expect -f
set timeout -1
set XSERVER [lindex $argv 0]
set XFILE [lindex $argv 1]
set uid "user"
set pwd "password"
set srcDir "/tmp/src"
set trgDir "/tmp/trg"
spawn /usr/ldir/bin/sftp ${uid}#${XSERVER}
expect -re "Are you sure you want to continue connecting (yes/no)?" {
send -- "yes\r"
expect -re "assword: "
send -- "$pwd\r"
} -re "assword: " {
send -- "$pwd\r"
}
expect -re "sftp> "
send -- "put ${srcDir}/${XFILE} ${trgDir}/test_file.txt\r"
expect "sftp> "
send -- "bye\r"
expect eof
catch wait result
exit [lindex $result 3]
Sample Output when there is no file available in source:
+ + date +%Y%m%d
DT=20160919
+ RXHOME=/tmp
+ x_server=A1234
+ x_file=test_20160919.dat
+ cd /tmp/scripts
+ ./test.exp A1234 test_20160919.dat
spawn /usr/ldir/bin/sftp user#A1234
Connecting to A1234...
user#A1234's password:
sftp> put /tmp/src/test_20160919.dat /tmp/trg/test_file.txt
File "/tmp/src/test_20160919.dat" not found.
sftp> bye
+ [ 0 -ne 0 ]
$
As you can see, even if there is no file transferred to target since it's not available in source, the script still exited with zero exit code.
You have to handle the error by yourself. Try like this:
set err 0
send -- "put ${srcDir}/${XFILE} ${trgDir}/test_file.txt\r"
expect {
-re "File .* not found" {
set err 1
exp_continue
}
"sftp> "
}
send -- "bye\r"
expect eof
exit $err
Why don't you just check that the file exists before spawning sftp?
set srcFile [file join $srcDir $XFILE]
if {! [file exists $srcFile]} {
puts stderr "File not found in source: $XFILE"
exit 1
}
spawn /usr/ldir/bin/sftp ${uid}#${XSERVER}
...

Connecting to switches using SSH and Expect

I'm a newbie to expect (lot's of shell programming though).
I'm trying to connect to a switch using expect to be able to dump it's running config. But somehow my send commands appear not be send to the switch.
Script:
#!/usr/bin/expect -f
proc connect {passw} {
expect {
"assword:" {
send "$passw\r"
expect {
"Prompt>" {
return 0
}
}
}
}
# timed out
return 1
}
set user [lindex $argv 0]
set passw [lindex $argv 1]
set host [lindex $argv 2]
#check if all were provided
if { $user == "" || $passw == "" || $host == "" } {
puts "Usage: <user> <passw> <host>\n"
exit 1
}
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host
set rez [connect $passw]
if { $rez == 0 } {
send {enable\r}
send {show running\r}
exit 0
}
puts "\nError connecting to switch: $host, user: $user!\n"
exit 1
Alas when I run the script (trigged from a shell script) I can see it login to the switch and see the 'Prompt', but after that the script seems to exit, because I don't see the 'enable' and 'show running' commands being executed.
Anyone can show me how to fix this?
Richard.
You need add further expect statement after sending the commands using send.
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host
set rez [connect $passw]
set prompt "#"
if { $rez == 0 } {
send {enable\r}
expect $prompt; # Waiting for 'prompt' to appear
send {show running\r}
expect $prompt; # Waiting for the prompt to appear
exit 0
}
Without the expect command after using send, then expect has nothing to expect from the ssh process and it will simply send the commands in a faster way which is why you are not able to get any output.
One more update :
You are using a separate proc for login purpose. If you are using a separate proc, then you are recommended to pass the spawn handle. Else, the script may fail. The reason is, instead of send/expect from the process, it will send/expect from the stdin. So, you should have used it like this,
proc connect {passw handle} {
expect {
-i $handle
"assword:" {
send "$passw\r"
expect {
-i $handle
"Prompt>" {
return 0
}
}
}
}
# timed out
return 1
}
set handle [ spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host ]
set rez [connect $passw $handle ]
set prompt "#"
if { $rez == 0 } {
send -i $handle "enable\r"
expect -i $handle $prompt
send -i $handle "show running\r"
expect -i $handle $prompt
exit 0
}
We are saving the ssh spawn handle into the variable 'handle' and it is being used with send and expect and the flag -i is used to make them to wait for the corresponding process
#Dinesh has the main answer. A couple of other notes:
send {enable\r} -- since you use braces instead of quotes, you are sending the characters \ and r and not a carriage return.
you return 0 and 1 from connect like you're a shell programmer (not an insult, I am one too). Use Tcl's boolean values instead.
A quick rewrite:
#!/usr/bin/expect -f
set prompt "Prompt>"
proc connect {passw} {
expect "assword:"
send "$passw\r"
expect {
$::prompt {return true}
timeout {return false}
}
}
lassign $argv user passw host
#check if all were provided
if { $user == "" || $passw == "" || $host == "" } {
puts "Usage: [info script] <user> <passw> <host>"
exit 1
}
spawn ssh -oStrictHostKeyChecking=no -oCheckHostIP=no $user#$host
if {[connect $passw]} {
send "enable\r"
expect $::prompt
send "show running\r"
expect $::prompt
send "exit\r" # neatly close the session
expect eof
} else {
puts "\nError connecting to switch: $host, user: $user!\n"
}

sending "ping" output to a variable

I am trying to ping some ip addresses in my router. I use this code:
for {set n 0} {$n < 10} {incr n} {puts [exec "ping 199.99.$n.1]}
but this will show the output.
the issue is that I don't want to see the output. I would like to send that output into another variable and the search the content of variable with "regexp" and get the result, and do the rest of the story.
but I don't know how I can do that.
Use the set command. The puts command prints it's argument.
set pingOutput [exec ping "199.99.$n.1"]
Or append if you want all IP's results in one variable.
set allPingOutput ""
for {set n 0} {$n < 10} {incr n} {
append allPingOutput [exec ping "199.99.$n.1"]
}
Try calling the ping with the -c flag:
ping -c 1 10.0.1.1
Not sure how to do it in tcl but in php for example:
It is very important to use ping -c1 <IP address> , otherwise the script will never end as the ping process never ends :)
My code uses an array of results of every IP
for {set i 2 } {$i < 10} {incr i} {
catch {if {[regexp {bytes from} [exec ping -c1 192.168.12.$i]]} {
set flag "reachable"
} else { set flag "not reachable"}
set result(192.168.12.$i) $flag
}
}
parray result
OUTPUT :
result(192.168.12.2) = reachable
result(192.168.12.3) = reachable
result(192.168.12.5) = reachable
result(192.168.12.6) = reachable
result(192.168.12.7) = reachable
result(192.168.12.9) = reachable
Instead of storing and manipulating , I used regexp .

Trouble with a terminal command using expect to send this

I'm having a problem with the command send in expect script.
I've never used the expect before, so there are so many peculiarity about it that I don't know, mainly about syntax.
Actually it catches the cpu usage btw.
top -n 10 -d 0.01 | awk 'BEGIN{FS="[,%]"; printf "(" }/^Cpu/{
gsub(/[^0-9.,]+/,"",$7); gsub(/^3949/,"",$7); printf $7" + "}
END{print 0") / 10"}' | bc
What I would like to do is: with expect, using the command send. I want pass that string by the script.
I'm trying on this way:
#!/bin/expect
set timeout 20
set user [lindex $argv 0]
set password [lindex $argv 1]
set host [lindex $argv 2]
set prompt "$ "
;
proc gestat { } {
;
send -- "echo -n 'MEMORY_FREE: ' && free -t | grep 'buffers/cache' | awk '{print \$4/(\$3+\$4) * 100}'\r"
send -- "top -n 10 -d 0.01 | awk \'BEGIN{FS='[,%]'; printf '(' }/^Cpu/{ gsub(/[^0-9.,]+/,'',\$7); gsub(/^3949/,'',\$7); printf \$7' + '} END{print 0') / 10'}\' | bc\r"
return
}
;
spawn ssh $user#$host
while (1) {
expect {
"(yes/no)? " {
send -- "yes\r"
}
"password: " {
send -- "$password\r"
}
"$prompt" {
gestat
break
}
}
}
expect "$prompt"
send -- "exit\r"
expect eof
But the send does not work. The error is:
invalid command name ",%"
while executing ",%"
(procedure "gestat" line 4)
I guess it would be because the expect "parser". But I really don't know.
Sorry for my poor english.
Since expect is an extension of Tcl, the [] characters are special in double quoted strings. You'll need to protect them.
Additionally, you'll get in trouble in the second call to awk -- you cannot embed single quotes in a single quoted string.
You need to use Tcl's {} quoting -- braces in Tcl quote strings like the shell uses single quotes:
send -- {echo -n 'MEMORY_FREE: ' && free -t | grep 'buffers/cache' | awk '{print $4/($3+$4) * 100}'}
send -- \r
send -- {top -n 10 -d 0.01 | awk 'BEGIN{FS="[,%]"; printf "(" }/^Cpu/{ gsub(/[^0-9.,]+/,"",$7); gsub(/^3949/,"",$7); printf $7 " + "} END{print 0 ") / 10"}' | bc}
send -- \r