How to match output spanning several lines - expect

I have a expect session that looks similar to this:
spawn myapp
stratos> list-tenants
Available Tenants:
+-----------+-----------+-------------+--------+---------------------------+
| Domain | Tenant ID | Email | State | Created Date |
+-----------+-----------+-------------+--------+---------------------------+
| frank.com | 1 | foo#bar.com | Active | 2014-02-26T11:33:23+05:30 |
+-----------+-----------+-------------+--------+---------------------------+
stratos>
The flow is roughly:
The application outputs stratos>
The user enters list-tenants
The application outputs Available Tenants: with the table shown in the example above.
How can I match the output from step 3 in expect? I.e. what do I need to replace ????, below?
Note that the expect script is part of a test with the output generated by a mock service that the application calls, so I am expecting to match the whole output verbatim.
expect "stratos>"
send "list-tenants\r"
expect {
???? { exp_continue; }
timeout { puts stderr "Expect could not match 'Available Tenants:'"; exit 1; }
}
Many thanks.

Try this: (untested)
spawn myapp
set prompt_re {stratos> $}
expect -re $prompt_re
send -- "list-tenants\r"
expect {
timeout {error "could not match available tenants"}
-re "list-tenants(.+)$prompt_re"
}
set available_tenants [string trim $expect_out(1,string)]

Related

Read program output from stdin instead of using spawn

I'm trying to write a shell function that spawns a ssh process and authentificates with a password. Then, I'd like to use the spawned process to do further stuff with expect.
Here's the function I have so far:
ssh_cmd() {
ssh $USER#$HOST 2>&1 | expect -c "
log_user 0
set timeout 5
expect password: {
send \"$PASS\n\"
sleep 2
log_user 1
}
"
}
And then I'd like to use given function in other places to interact with the ssh process like this:
ssh_cmd | expect -c "
expect '#' {
send pwd\n
send exit\n
}
expect eof
"
However, running the ssh_cmd function with -d option for expect I get the following result:
expect version 5.45.4
expect: does "" (spawn_id exp0) match glob pattern "password:"? no
ubnt#ui1's password: expect: timed out
From what I understand, the output of ssh does not get piped correctly. I know the common way to do this would be to use spawn, but that would mean the process would get killed after expect exits and I could not have a generic function that authentificates ssh sessions and keeps the process alive for further usage.
What you're designing won't work. Expect needs the process to be spawned from within the expect interpreter using the spawn command. Passing the command's stdout into expect is insufficient.
You could try this:
ssh_cmd() {
# a default bit of code if user does not provide one.
# you probably want some checking and emit an error message.
local user_code=${1:-set timeout 1; send "exit\r"; expect eof}
expect -c "
log_user 0
set timeout 5
spawn ssh -l $USER $HOST
expect password: {
send \"$PASS\n\"
sleep 2
log_user 1
}
$user_code
"
}
and then invoke it like:
ssh_cmd '
expect "#" {
send pwd\r
send exit\r
}
expect eof
'
Note that single quotes have no special meaning in expect, they are just plain characters: you probably don't want to expect the prompt to be the 3 character pattern '#'

expect script - how to split the output of a command into several variables

I am trying to set up a expect script that logs in to a remote server and
fetches the 3 last created logfiles. The output (1 line) looks like below:
root#server1:/cluster/storage/var/log/alarms$
Last 3 created files are: FmAlarmLog_20180515_1.log FmAlarmLog_20180516_2.log FmAlarmLog_20180517_3.log
How can I split this output and create 3 different variables (one for each logfile) from this output?
The name of the logfiles always starts with "FmAlarmLog_"
I need to add later the part handling the fetching of those files.
#!/usr/bin/expect -f
set passwd "xxx"
set cmd1 "ls -ltr | tail -3 | awk '{print \$NF}'"
set dir "/cluster/storage/var/log/alarms"
set timeout 1
set prompt1 "*\$ "
log_user 0
spawn ssh admin#10.30.35.36
expect {
-re ".*Are.*.*yes.*no.*" {
send "yes\n"
exp_continue
}
"*?assword:*" {
send $passwd
send "\n"
}
}
expect $prompt1 { send "cd $dir\r"}
expect $prompt1 { send "$cmd1\r"}
set Last3LogFiles {}
expect \n
expect {
-re {^([^\r\n]*)\r\n} {
lappend Last3LogFiles $expect_out(1,string)
exp_continue
}
-ex $prompt1
}
send_user "Last 3 created files are: $Last3LogFiles\n"
send "exit\n"
exit 0
Try this:
expect $prompt1
send "$cmd1\r"
# express the prompt as a regular expression.
# best practice is to match the prompt at the end of the current input,
# I don't know if you have a space at the end of your prompt
expect -re {(.*)\r\n\$ ?$}
# command output is in $expect_out(1,string)
set Last3LogFiles [regexp -inline -all {FmAlarmLog_\S+} $expect_out(1,string)]

How can I remove special characteres from expect?

So I'm using the next expect script in order to get the running-config of a switch and then save it to a file:
#!/usr/bin/expect
set timeout 9
set username [lindex "foo"]
set password [lindex "bar"]
set hostname [lindex "1.2.3.4"]
spawn ssh -q $username#$hostname
expect {
timeout { send_user "\nTimeout!\n"; exit 1 }
"*assword"
}
send "$password\n"
expect {
timeout { send_user "\nWrong password\n" ; exit 1 }
"switch-00>"
}
send "enable\n"
expect {
"switch-00#"
}
send "show running-config\n"
log_file -noappend log.txt
expect {
-ex "--More--" { send -- " "; exp_continue }
"*#" { log_file; send "exit\r" }
}
send "exit\n"
close
It works as it should except for this:
--More--^H ^H^H ^H^H ^H^H ^H^H ^H^H ^H^H ^H^H ^H
which is appearing in log.txt every time "--More--" gets printed.
It's not an issue to remove "--More--" later on using bash but if I do:
grep "^H" log.txt
there's no output, so I cannot remove it as it doesn't match.
I was trying to find a way to not output special characteres with expect if possible but didn't find any, so I'm asking here in case anyone knows.
A solution using bash would help me aswell but using expect is prefered.
You could use the bash tr utility. From the man page
NAME
tr -- translate characters
DESCRIPTION
The tr utility copies the standard input to the standard output with sub-
situation or deletion of selected characters.
SYNOPSIS
tr [-Ccsu] string1 string2
tr [-Ccu] -d string1
-C Complement the set of characters in string1, that is ``-C ab''
includes every character except for `a' and `b'.
-c Same as -C but complement the set of values in string1.
-d Delete characters in string1 from the input.
To Strip out non-printable characters from file1.
tr -cd "[:print:]\n" < file1 # This is all you need.

Code to count number of types of files on client in Unix

Hi I am new to shell scripting.
my requirement is:
There is one server & 3 clients. On each client error logs files are generated which are of 4 types.Say type 1 error , type 2 error like this type 4 error.
I want to write a script which read all the 3 clients from server & provide me number of times 4 different type of error logs are genereted on each client.
In short it should use ssh & grep command combination.
I have written the demo script but it's not providing me the number of times different type of logs occured on clients.
#error[1]='Exception: An application error occurred during an address lookup request, please contact IT'
#error[2]='SocketTimeoutException: Read timed out'
#error[3]='Exception: The search has produced too many matches to be returned'
#error[4]='Exception: No matching address found'
error_1='exception 1'
error_2='exception 2'
function get_list_of_clients()
{
NUM_OF_CLIENTS=$(wc -l ${CLIENT_IP_LIST} | awk -F " " '{ print $1 }' )
echo $NUM_OF_CLIENTS
if [ "${NUM_OF_CLIENTS}" -gt 0 ]
then
for ((row=2; row<=$NUM_OF_CLIENTS; row++))
do
CLIENTS_IP=$(sed -n ${row}p ${CLIENT_IP_LIST}| awk -F " " '{print $3 }')
echo ${CLIENTS_IP}
# get_number_of_errors
# copy_count_errors
echo ${$error_$row}
done
fi
}
function get_number_of_errors()
{
for((row_no=1; row_no<=4; row_no++))
do
{
/usr/bin/expect - <<- EndMark
spawn ssh root#${CLIENTS_IP} "grep $error[$row_no] var/error.log |wc -l" >> /tmp/${CLIENTS_IP}_error${row_no}.txt
match_max 50000
expect {
"*yes/no*" {
send -- "yes\r"
send -- "\r"
exp_continue
}
"*?assword:*" {
send -- "${CLIENT_PASSWORD}\r"
send -- "\r"
}
}
expect eof
EndMark
}
done
}
function copy_count_errors()
{
/usr/bin/expect - <<- EndMark
spawn scp root#${CLIENTS_IP}:/tmp/${CLIENTS_IP}* /tmp/
match_max 50000
expect {
"*yes/no*" {
send -- "yes\r"
send -- "\r"
exp_continue
}
"*?assword:*" {
send -- "${CLIENT_PASSWORD}\r"
send -- "\r"
}
}
expect eof
EndMark
}
get_list_of_clients
================================================================================
please help.
This is not really an answer, an attempt to help you getting your own.
The Problem
If I understand it correctly:
Your script runs on the server
You have three clients, each has log files
Your list of clients is in a file named $CLIENT_IP_LIST, where the IP is the third field and the first line is some sort of header, which you want to exclude.
Suggestions
It seems you need to ssh and scp to the clients. If possible, I suggest setting up SSH Public Key Authentication between your server and clients. This will greatly simplify your scripts.
Use the -C flag for compression with the scp and ssh commands.
You should copy the log files from the clients to the server and do processing on the server.
If you can choose a different scripting language such as Python, Tcl (You used Expect, which is really Tcl). Bash can handle your problem, but you will find other languages work better. This is my opinion, of course.
Get one piece of your puzzle to work before moving on to the next. For example, right now, your get_list_of_clients() function does not yet working.
That being said, here is a rewrite of get_list_of_clients, which I tested and it works within my assumptions about your $CLIENT_IP_LIST file:
function get_list_of_clients() {
let row=1
while read line
do
if (( row > 1 ))
then
set $line # Breaks line into pieces
CLIENT_IP="$3"
echo "$CLIENT_IP"
fi
let row=row+1
done < $CLIENT_IP_LIST
}

Expect script clause evaluation

In my expect script, my goal is to send a command to show the properties of the two processors on a motherboard. Please assume the remote logging in is successful. It's where the send clause variables are not evaluated successfully.
I have a procedure and a variable:
set showcpu "show -d properties /SYS/MB/P\r"
I created a while loop to execute do a "send" if the "cpu" count starts at 0 and less than 2.
set cpu 0
while { $cpu < 2 } {
expect {
-re $prompt {send "${showcpu}${cpu}\r"; }
timeout {
my_puts "ILOM prompt timeout error-2" [ list $fh1 $fh3 stdout ]
exit 1
}
}
set cpu [ expr {$cpu + 1} ]
}
The execution result is this:
[BL0/SP]-> show -d properties /SYS/MB/P
show: Invalid target /SYS/MB/P
[BL0/SP]-> 0
Invalid command '0' - type help for a list of commands.
I wanted the script to combine the value $showcpu with $cpu and it should look like this:
show -d properties /SYS/MB/P0 and show -d properties /SYS/MB/P1.
Could someone please educate me on what I need to do to accomplish that?
The variable ${showcpu} itself already contains "\r" (according to 1.).
Either define it without "\r":
set showcpu "show -d properties /SYS/MB/P"
or use string trim (http://wiki.tcl.tk/10174):
send "[string trim ${showcpu}]${cpu}\r"
I would recommend to trim the white spaces on the place where the variable is set, not at the places where the variable is used.

Resources