Expect: how to grab part of output - expect

I'm a bit new to expect programming, so I need help.
Here's a sample session:
CMD> disable inactive
Accounts to be disabled are:
albert_a - UUID abcd-11-2222
brian_b - UUID bcde-22-3333
charley_c - UUID cdef-33-4444
Starting processing
...hundreds of lines of processing...
CMD> quit
Done.
I need to grab the username and UUIDs there (the UUIDs are not available through other means), then either save them into a file. How do I do that in expect?
Edit: the - UUID (space dash space "UUID") part of the list is static, and not found anywhere in the "hundreds of lines of processing", so I think I can match against that pattern... but how?

Assuming the answer to my question in the comments is 'yes', here's my suggestion.
First, you need to spawn whatever program will connect you to the server (ssh, telnet, or whatever), and login (expect user prompt, send password, expect prompt). You'll find plenty samples of that, so I'll skip that part.
Once you have done that, and have a command prompt, here's how I would send the command and expect & match output:
set file [open /tmp/inactive-users w] ;# open for writing and set file identifier
send "disable inactive\r"
expect {
-re "(\[a-z0-9_\]+) - UUID" { ;# match username followed by " - UUID"
puts $file "$expect_out(1,string)" ;# write username matched within parenthesis to file identifier
exp_continue ;# stay in the same expect loop in case another UUID line comes
}
-re "CMD>" { ;# if we hit the prompt, command execution has finished -> close file
close $file
}
timeout { ;# reasonably elegant exit in case something goes bad
puts $file "Error: expect block timed out"
close $file
}
}
Pls note that in the regexp I'm assuming that usernames can be composed of lowercase letters, numbers and underscores only.
If you need help with the login piece let me know, but you should be ok. There are plenty of samples of that out there.
Hope that helps!

Related

expect doesn't match pattern correctly

I tried to automate the easyrsa request generation via expect. I came up with that bash script:
#!/bin/bash
firstname=$1
lastname=$2
mail=$3
department=$4
password=$5
[...]
cd /VPN-CA/
/usr/bin/expect -c "
spawn ./easyrsa gen-req $mail
expect \"Enter PEM pass phrase:\"
send \"$password\r\"
expect \"Verifying - Enter PEM pass phrase:\"
send \"$password\r\"
expect \"Country Name (2 letter code) \[DE\]:\"
send \"\r\"
expect \"State or Province Name (full name) \[MyState\]:\"
send \"\r\"
expect \"Locality Name (eg, city) \[MyCity\]:\"
send \"\r\"
expect \"Organization Name (eg, company) \[MyOrganization\]:\"
send \"\r\"
expect \"Organizational Unit Name (eg, section) \[MyDepartment\]:\"
send \"$department\r\"
expect \"Common Name (eg: your user, host, or server name) \[$mail\]:\"
send \"$firstname $lastname\r\"
expect \"Email Address \[email#address.de\]:\"
send \"$mail\r\"
expect eof
"
# do somethin else
[...]
exit 0
The script works and the request will be generated correctly, but expect is very slow. With -d it shows me for every expected pattern after the second password query something like that:
expect: does "test\r\n\r\n-----\r\nYou are about to be asked to enter information that will be incorporated\r\ninto your certificate request.\r\nWhat you are about to enter is what is called a Distinguished Name or a DN.\r\nThere are quite a few fields but you can leave some blank\r\nFor some fields there will be a default value,\r\nIf you enter '.', the field will be left blank.\r\n-----\r\n" (spawn_id exp3) match glob pattern "Country Name (2 letter code) [DE]:"? no
Country Name (2 letter code) [DE]:
expect: does "test\r\n\r\n-----\r\nYou are about to be asked to enter information that will be incorporated\r\ninto your certificate request.\r\nWhat you are about to enter is what is called a Distinguished Name or a DN.\r\nThere are quite a few fields but you can leave some blank\r\nFor some fields there will be a default value,\r\nIf you enter '.', the field will be left blank.\r\n-----\r\nCountry Name (2 letter code) [DE]:" (spawn_id exp3) match glob pattern "Country Name (2 letter code) [DE]:"? no
expect: timed out
send: sending "\r" to { exp3 }
I don't understand why the pattern doesn't match. I mean whilst the reply is send nevertheless after the timeout it works, but it is slow as hell... (and it is crap)
Has anybody an explanation or a solution for this problem?
The problem in your code is that the shell removes the backslashes while constructing the string, so expect sees un-escaped brackets -- a command substitution. In your code, you'll have to double backslash the opening brackets.
The same problem happens here:
expect -c "
...
send \"$password\r\"
Suppose $password is "1234", then expect will see:
send "1234r" ;# <= no carriage return!
Using a double quoted string to hold the expect code leads very quickly to quoting hell. Use a here-doc instead:
/usr/bin/expect <<END_EXPECT
spawn ./easyrsa gen-req $mail
expect "Enter PEM pass phrase:"
send "$password\r"
expect "Verifying - Enter PEM pass phrase:"
send "$password\r"
expect "Country Name (2 letter code) \[DE\]:"
send "\r"
expect "State or Province Name (full name) \[MyState\]:"
send "\r"
expect "Locality Name (eg, city) \[MyCity\]:"
send "\r"
expect "Organization Name (eg, company) \[MyOrganization\]:"
send "\r"
expect "Organizational Unit Name (eg, section) \[MyDepartment\]:"
send "$department\r"
expect "Common Name (eg: your user, host, or server name) \[$mail\]:"
send "$firstname $lastname\r"
expect "Email Address \[email#address.de\]:"
send "$mail\r"
expect eof
END_EXPECT
If you use braces (expect's single quoting mechanism) you don't have to escape the brackets:
expect {Email Address [email#address.de]:}
Just to troubleshoot further, could you just check does the system is slow in general or only while running the expect command.
Sometimes because of delay in DNS name resolution also the system response slowly.
Check and remove any unnecessary entries in the /etc/resolve.conf and then try.
It seems to be a problem of the escaping of \[, the closing square bracket doesn't need to be escaped.
This fails to detect the line.
expect \"Country Name (2 letter code) \[DE\]:\"
But you can replace it with a wildcard ?
expect \"Country Name (2 letter code) ?DE]:\"
Or you could escape the opening square bracket with only 6 backslashes:
expect \"Country Name (2 letter code) \\\\\\[DE]:\"

TCL script only saves the bottom half of the output

I have script that logs into devices and runs a show command.
I then set this output to a variable:
set output $expect_out(buffer)
and then print the variable to a file:
puts $fileId $output
When the script is run, I can see the whole output being generated, however in the file, only the bottom half of the output is saved.
This is probably because the buffer is reaching its limit. This show command is running right after another lengthy show command.
I tried using unset expect_out(buffer) but this still does not make a difference.
I also tried this solution http://wiki.tcl.tk/2958 and it still did not work (returns an error)
How can I get the script to store all of the output?
I see in the expect man page that the pattern full_buffer will match when the size of the buffer reaches match_max bytes, so you can do something like:
match_max 16000
# ...
expect {
full_buffer {
puts $fileid $expect_out(buffer)
exp_continue
}
"whatever you are currently expecting"
}
puts $fileid $expect_out(buffer)
You can also make use of the log_file to make it simple. You can control when to save and when to stop logging.
Have a look at here to know about the same.

The '-i' flag in expect

In the page that describes Expect, it is written:
For example, the following example waits for "connected" from the
current process, or "busy", "failed" or "invalid password" from the
spawn_id named by $proc2.
expect {
-i $proc2 busy {puts busy\n ; exp_continue}
-re "failed|invalid password" abort
timeout abort
connected
}
As far as I understand, everything in that expect is relevant only to the spawn_id named by $proc2, while the current spawn_id isn't relevant.
That's because that the -i flag (as written prior to the first quotation):
... declares the output from the named spawn_id list be matched
against any following patterns (up to the next -i).
Perhaps the code is not written as intended?
Dor, I checked THE Expect book (Don Libes's "Exploring Expect") and you are correct.
If the -i flag is used in an expect block, then everything within that block will attempt to match the output from the spawned process with the id indicated after -i.
So, according to Don Libes, what that page says is wrong. And I would go with Don Libes on this one. :-)
Maybe you can report it to them so they can fix it?

Arduino returning more responses than queries have been sent

I have a problem when using Arduino to post data to Pachube. The Arduino is configured to return JSON data for the temperature when you send a 't' and return JSON data for the light level when you send an 'l'. This works perfectly through the Arduino Serial Monitor. I then created two bash scripts. One regularly sends the 't' and 'l' commands to Arduino and waits 10 seconds in between each request.
while true; do
echo -n t > /dev/ttyACM0
echo "$(date): Queried Arduino for temperature."
sleep 10
echo -n l > /dev/ttyACM0
echo "$(date): Queried Arduino for light."
sleep 10
done
This works fine. I get an echo message every 10 seconds. The other script reads the generated JSON from serial port (I basically copied it from some Web page).
ARDUINO_PORT=/dev/ttyACM0
ARDUINO_SPEED=9600
API_KEY='MY_PACHUBE_KEY'
FEED_ID='MY_FEED_ID'
# Set speed for usb
stty -F $ARDUINO_PORT ispeed $ARDUINO_SPEED ospeed $ARDUINO_SPEED raw
exec 6<$ARDUINO_PORT
# Read data from Arduino
while read -u 6 f ;do
# Remove trailing carriage return character added
# by println to satisfy stupid MS-DOS Computers
f=${f:0:${#f} - 1}
curl --request PUT --header "X-PachubeApiKey: $API_KEY" --data-binary "{ \"version\":\"1.0.0\", \"datastreams\":[ $f ] }" "http://api.pachube.com/v2/feeds/MY_FEED_ID"
echo "$(date) $f was read."
done
Unfortunately, this script goes crazy with echo messages telling me several times per 10 seconds that it posted data to Pachube although it should only do it every 10 seconds (whenever the first script told Arduino to create a JSON message). I thought it might be an issue with buffered messages on the Arduino but even when switching it off and on again the problem remains. Any thoughts? Thanks in advance.
I am completely unfamiliar with Arduino and a handful of other things you're doing here but here are a few general things I see:
Bash is almost entirely incapable of handling binary data reliably. There is no way to store a NUL byte in a Bash string. Looks like you're trying to pull some trickery to make arbitrary data readable - hopefully you're sending nothing but character data into read, otherwise this isn't likely going to work.
read reads newline-delimited input (or the given value of -d if your bash is new enough). I don't know the format the while loop is reading, but it has to be a newline delimited string of characters.
Use read -r unless you want escape sequences interpreted. (You almost always want -r with read.)
Unconditionally stripping a character off the end of each string isn't the greatest. I'd use: f=${f%+($'\r')}, which removes 1 or more adjacent \r's from the end of f. Remember to shopt -s extglob at the top of your script if this isn't the default.
This shouldn't be actually causing an issue, but I prefer not using exec unless it's really required - which it isn't here. Just put done <$ARDUINO_PORT to terminate the while loop and remove the -u 6 argument from read (unless something inside the loop is specifically reading from stdin and can't conflict, which doesn't appear to be the case). The open FD will automatically close when exiting the loop.
Don't create your own all-caps variable names in scripts because they are reserved and can conflict with variables from the environment. Use at least one lower-case letter. This of course doesn't apply if those variables are set by something in your system and you're only using or modifying them.

How to improve this FTP (shell) function?

I have about a ton of scripts using the following function:
# Copies files over using FTP.
# Configurations set at the beggining of the script
# #param $1 = FTP Host
# $2 = FTP User
# $3 = FTP User password
# $4 = Source file name
# $5 = destination directory
# $6 = local directory
doftp() {
log_message_file "INFO" "Starting FTP"
ftp_hst=$1
ftp_usr=$2
ftp_pwd=$3
sourcefile=$4
destdir=$5
locdir=$6
ftp -nv $FTPH << EOF 2> ftp.err.$$
quote USER $ftp_usr
quote PASS $ftp_pwd
cd $destdir
lcd $locdir
bin
put $sourcefile
bye
EOF
if [ "$(wc ftp.err.$$|cut -d" " -f8)" != 0 ] ; then
log_message_file "ERROR" "Problem uploading files: $(cat ftp.err.$$)"
else
log_message_file "INFO" "FTP finished"
fi
rm ftp.err.$$
}
It works, does it's job, unless ftp fails. Fortunately for me, the scripts are quite precise and the FTP almost never fails. But this is one of these rare moments when one has the chance (time) to go back and review code marked in the TODO list. Only problem is that I'm not too sure on how to improve it... I'd take you guys recommendations on what to change there.
One obvious problem is the error parsing from the ftp, which is a totally lame. But I'll also take thoughts in other parts of the function :)
Worth mentioning this is run in a AIX server? Oh, and no, I cannot use SFTP :(
Thanks for any input!
ps.: log_message_file is just a basic logging... has no effect in the function.
good documentation
good variable names
good indentation, you might want to read about <<-EOF (the '-' being the item I intend to highlight with this comment. Using that feature allows you to indent (must use tab chars) the whole here-document to match indenting of rest of script.
good use of tmp filename with $$ (depending on how much this function gets used, you might want to append the parent script name as part of the tmp name, to further disambiguate, but low priority)
good use of $(cat ftp.err.$$) i.e. actually showing the error message, rather than just a message like 'error occured' (I see this all the time, what error? what was the msg?!)
you could extend the ftp service to use mput, but then you have to understand the vagaries of your particular ftp client AND make a note to your self that any time there is a change of ftp client you'll need to check if your mput ${fileNames} variable still works as you expect.
possibly the one place to think about improvement would be to use a case statement to parse the STDERR output, but again, the added benefit may not be worth the maintenance cost in the future.
errMsgs="$(cat ftp.err.$$)"
case "${errMsgs}" in
*warningStrings* ) print "warning found, msg was ${errMsg} ;;
*errorStrings* ) print "error found, msg was ${errMsg} ;;
*fatalStrings* ) pring "fatal error found, can't continue, msg was ${errMsg} ;;
esac
I hope this helps.

Resources