Monday, December 22, 2014

A tale of two scripts redux: joining Python and expect-lite

Python wrapper
As I mentioned earlier, expect-lite interpreter is quite basic. The advantage of such simpleness is that it ignores anything it doesn't understand. In a tale of two scripts I explained how to join bash and expect-lite in the same script. But the technique can also be applied to other languages.

In this post, I'll show how expect-lite can be embedded in a Python script allowing you to get all the goodness of Python and expect-lite.

First we start with a simple Python script which just makes a call to subprocess (used to execute an external application), prints the output of the expect-lite script in real time and then checks the return code of the subprocess.

#!/usr/bin/env python
"""
Example script: embedding expect-lite in python
    22 December 2014 -- Craig Miller
"""

import subprocess, inspect, os

def embed():
    script_name = inspect.getfile(inspect.currentframe())
    process = subprocess.Popen('expect-lite ' + script_name, 
                                shell=True, stdout=subprocess.PIPE)
    # print stdout in real time
    line = None
    while line != '':
        line = process.stdout.readline()
        print "EL:", line.rstrip()
    out, err = process.communicate()
    if process.returncode > 0:
        print("========= ERROR:%s" % process.returncode)
    else:
        print("========= GOOD:%s" % process.returncode)

#############################
# beginning of python script

if __name__ == '__main__':
    try:
        embed()
    except KeyboardInterrupt:
        print "Detected ^C"
        os._exit(1)

If you are familiar with Python, then you know that indentation is more than just a a good idea, it is required by the language. The script above has the basic components of a Python script, but the important part is the function embed()

In embed(), it figures out the name of the script using inspect.getfile() and saves it in the variable script_name. Then subprocess.Popen() is called with expect-lite and the script_name, to recursively run the script again, this time using expect-lite as the interpreter. 

However, before we do that, we need to add the expect-lite part of the script. At the bottom of the Python script add:
if False:'''
#############################
# beginning of expect-lite code

*EXP_INFO
$count=3
; === test of EL
>echo "inside expect-lite! whoo-hoo"
<inside
@5
;purple === ping loopback
>ping6 -c $count ::1
<packets transmitted
>echo "Continue"
>
; === pau
'''

The trick is to protect the expect-lite lines from the Python interpreter. The Python interpreter is a bit smarter than the bash interpreter, which does not read the entire file before executing. So to make python happy, we must shield the expect-lite lines inside an if false statement. The triple quotes is a Python mechanism that allows anything to be entered, even expect-lite. Save the completed script as two_scripts.py

The expect-lite part of the script is a simple one, showing that we are actually inside the expect-lite script, and a simple ping6 of the IPv6 loopback address.

Because of the dual nature of the script, it is possible to run (this example) without any Python, by running the script directly from expect-lite.
expect-lite two_scripts.py

Or run it using Python:
python two_scripts.py

The expect-lite part of the script can be located anywhere in the python script, as long as if false is used to protect it. Of course, you can also have Python pass parameters to expect-lite using CLI constants, making the script even more flexible and useful. 

There you have it, two languages, one script, all the power of Python, all the simplicity of expect-lite, automation for the rest of us.

Happy Holidays!

Wednesday, November 19, 2014

Sawing Logs

Sawing Logs the way you like them
Recording what happened for later review is called logging. For years expect-lite lacked native logging, and relied on other programs like 'script' or 'tee' to record the output. Logging was added to make it easier to record just the parts you might want to keep for later.

expect-lite uses directives to control behavior of operation. Directives always start with an asterisk, and all CAPS, like *INFO. The *LOG, *NOLOG and *LOGAPPEND directives added native logging support in 2013. Like all directives, the log commands can be used on the command line when starting a script, or within the script to just log the desired portion.

*LOG

The *LOG will automatically create a log file with the <script_name>.log in the script directory. But *LOG can also take a path/filename parameter to log to a different directory.
$path=/tmp
; === get today's date
>date %F
+$today=\n(\d+-\d+-\d+)
*LOG $path/$arg0-$today.log
>do stuff


*NOLOG

And the *NOLOG stops the logging. For example, perhaps there is a while loop that is polling for some event, you don't really need to see that it polled 500 times in the log, you just want to see (in the log) what happens after the event.
...
*LOG $path/myfile-$today.log
>do stuff
*NOLOG
[ $state != $event
   ; poll for event
   >show event
   +$state=(enabled|disabled)
]
*LOGAPPEND $path/$arg0-$today.log
...

And *LOGAPPEND will append to an exiting log file, or create a new file, if there is no existing file.

What is logged

The *LOG log file will capture everything that is seen on the terminal screen, expect-lite messages such as *INFO, *WARN, commands typed in during an *INTERACT (a breakpoint), and all standard out, including colour. (hint use *NOCOLOUR to disable colour output).

Plays well with Instant Interact

Why not just use 'script' or 'tee' commands to record the script output. No reason, if that it working for you. However *LOG provides better control over what goes into the log file. Additionally, I had real problems using 'tee' and Instant Interact (creating a breakpoint on the fly) with Ctrl+\, as it would terminate the 'tee' command (and in turn the running script). Using native logging with *LOG allows you to use Ctrl+\ (control backslash) at any point during the script, giving you immediate access to the running script without having to preset a breakpoint.

So go ahead and saw those logs up anyway you like, because expect-lite is automation for the rest of us.

Note 1: the pre-defined variable $arg0 holds the script name
Note 2: for those who don't require 'U's in colour, *NOCOLOR will also work

Sunday, October 19, 2014

Capturing the State of Mind

State of Mind
There are things out there which have state. Think of your gas tank, could be full or empty. Maybe it is an interface that is up or down, or initializing. You may need to know the state before your expect-lite script can continue.

In this blog entry I'll cover a key tip of grabbing just the state of a device by using one of the simple 7 terms from regex, the OR.

For example, perhaps you want to test an ethernet interface, but it would be really useful if the interface such as wlan0 was UP before starting your test.

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN mode DEFAULT
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: wlan0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 00:16:cb:b4:7c:52 brd ff:ff:ff:ff:ff:ff



Or the ifconfig command, however it doesn't show the interface as DOWN.

$ /sbin/ifconfig -a

wlan0     Link encap:Ethernet  HWaddr 00:16:cb:b4:7c:52 
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 

          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)


So using a dynamic variable we can capture the state of the interface, letting expect-lite to do all the hard work of parsing through the output. Since the 'ip' command is less typing, and newer, I"ll use it in my examples.

$interface
>ip link
<$interface
+$interface_state=state (UP|DOWN)


There are a few things occurring in this short script which are covered in older posts:
   1. $interface by assigning it to a variable, it is possible to use the power of constants to override the variable on the command line, making the script more flexible.
   2. <$interface is an expect line, which consumes the output of the 'ip link' command. Therefore making the dynamic variable capture on the next line much easier. The first state encountered in the remaining output of the 'ip link' command is the one we want.
   3. Lastly, and this is the key to this post,  (UP|DOWN) is using the regex OR '|' which means: capture either the word 'UP' or the word 'DOWN' and nothing else. The variable $interface_state is guaranteed to only be one of those states.

Now that it is easy to get the state of an interface how do we wait for the interface to change state? With a while loop, of course. Let's expand on the script we have above.

$interface
>ip link
<$interface
+$interface_state=state (UP|DOWN)
# while $interface_state is not equal to UP
[ $interface_state != UP
  !sleep 2
  >ip link
  <$interface

  +$interface_state=state (UP|DOWN)
]
; === The $interface is now $interface_state


Of course this could be improved, such as if the interface never comes 'UP', the script will spend a very long time in the while loop (eventually it will hit the infinite loop protection, and stop). Typically I add a counter variable, and check if the counter variable has exceeded a maximum number. But I'll leave that to you or another post.

When capturing states in expect-lite, it is important to put all the states in capture parens e.g. (UP|DOWN|INIT|RESET) by doing this, you are guaranteed to always capture the state.

Automating state is easy with expect-lite. State of mind... is a little harder.

Friday, May 30, 2014

Saving several slices of output for later with Pseudo Arrays

Pseudo Array of bikes
I was recently talking to a group of expect-lite users who wanted to know how to save several slices of the output of a command, and then be able to use those slices later on. A simple while loop and a pseudo array would be a good solution to this problem.

What is a pseudo array you ask? Well it acts like an array, but it isn't an array in the strictest sense. A real array would have the form $var(index), e.g. $myvar(5). 

But expect-lite doesn't support real arrays, the format for pseudo array is $var$index e.g. $myvar$i. In fact, when using pseudo arrays, new variables names are automatically being created, e.g. $myvar1, $myvar2 .. $myvar100, etc.

An example problem is to collect the block devices (like hard drives) in a pseudo array to be further examined later. In this example, I'll use the ls command to display the devices in /dev. The ones which are block devices start with a 'b'. An example output will look like:
$ ls -l /dev
crw------- 1 root wheel 1, 0 May 4 07:14 auditpipe
crw------- 1 root wheel 13, 0 May 4 07:15 autofs
crw------- 1 root wheel 18, 0 May 4 07:15 autofs_control
crw-rw-rw- 1 root wheel 17, 3 May 4 07:15 autofs_nowait
crw------- 1 root wheel 23, 0 May 27 08:11 bpf0
crw------- 1 root wheel 23, 1 May 27 08:11 bpf1
brw-r----- 1 root operator 14, 0 May 4 07:14 disk0
brw-r----- 1 root operator 14, 2 May 4 07:14 disk0s1
brw-r----- 1 root operator 14, 1 May 4 07:14 disk0s2
brw-r----- 1 root operator 14, 3 May 4 07:14 disk0s3
brw-r----- 1 root operator 14, 4 May 4 07:14 disk0s4
brw-r----- 1 root operator 14, 5 May 4 07:14 disk0s5
brw-r----- 1 root operator 14, 6 May 4 07:14 disk0s6
brw-r----- 1 root operator 14, 7 May 4 07:14 disk0s7
crw-rw-rw- 1 root wheel 4, 0 May 4 07:14 ttyp0
crw-rw-rw- 1 root wheel 4, 1 May 4 07:14 ttyp1
crw-rw-rw- 1 root wheel 4, 2 May 4 07:14 ttyp2
crw-rw-rw- 1 root wheel 4, 3 May 4 07:14 ttyp3
brw------- 1 root operator 1, 0 May 4 07:14 vn0
brw------- 1 root operator 1, 1 May 4 07:14 vn1
brw------- 1 root operator 1, 2 May 4 07:14 vn2
brw------- 1 root operator 1, 3 May 4 07:14 vn3
...

For this example, I'll use a while loop to iterate through the output, and increment the pseudo array index variable, capturing the block devices into a pseudo array of dynamic variables. If the dynamic variable can not find a block device, then the variable will be set to a special expect-lite value of __NO_STRING_CAPTURED__. The while loop will continue looping until there are no more block devices.

After capturing the device in pseudo array variable $dev$i, the script will consume the output (see consuming tables for lunch) to remove the top part of the output. That will leave the next block device to be captured near the top of the blob of text output for the next iteration of the while loop. I'll use some of the basic regex such as \n, \d, and \w (see demystify regex with 7 simple terms) to ensure the line begins with 'b' and ends with the desired device name. Only the part in parens, (\w+), is captured into the dynamic variable (see expect-lite variables).

>ls -l /dev
# initialize index variable
$i=0
# initialize the first element in the pseudo array
$dev$i=none
# while loop testing the pseudo array value is captured
[ $dev$i != __NO_STRING_CAPTURED__
    +$i
    # capture the block device
    +$dev$i=\nb.*\d\d:\d\d (\w+)
    # expect the device to consume the output
    ? $dev$i != __NO_STRING_CAPTURED__ ? <$dev$i
]

# set max devices captured
$max=$i

Now that the block devices have been captured into a pseudo array, I'll explore them a bit more using the file command. Using another while loop to iterate through the pseudo array re-using the index.

# initialize index variable
$i=1
# while loop to check the devices in the pseudo array
[ $i < $max
    # show the pseudo array value - the device
    >file $dev$i
    # check that it is a block special device
    <block special
    +$i
]

Of course you can always look at the pseudo array (and all the expect-lite variables) in the IDE (debugger) by typing <esc>v or you can print it out right from the script using the directive *SHOW VARS. Depending on your system block devices, the output would look something like:
$ DEBUG Info: Printing all expect-lite variables
Var:arg0 Value:test_pseudo_array.txt
Var:dev0 Value:none
Var:dev1 Value:vn0
Var:dev2 Value:vn1
Var:dev3 Value:vn2
Var:dev4 Value:vn3
Var:dev5 Value:__NO_STRING_CAPTURED__
Var:i Value:5
Var:max Value:5

So, not only can you eat your output for lunch, but you can save the slices in a pseudo array for a midnight snack. expect-lite, serving up automation for the rest of us.

Sunday, April 20, 2014

Warm and Fuzzy

Life can be Fuzzy
One particular problem I have been grappling with is how to do an approximate check of a value. Being a user, as well as the maintainer of expect-lite, I am interested in improving expect-lite with the goal to make things easier.

Say for example, you want to check the load on a machine, but if you have every looked at load, you know it isn't an exact number. The first load average number represents the average load on the machine in the past minute (the other two are averages over 5 and 15 minutes).
$ uptime
 17:10:17 up 98 days,  7:21, 12 users,  load average: 1.39, 1.34, 1.03

 
Back to checking the load of the machine. With expect-lite you could capture the value into a dynamic variable, and then use an if statement to do a comparison, which is a very programatic approach.
>uptime
<load average
+$load=(\d\.\d+)
# fail if load is higher than 3.00
? $load > 3? *FAIL

But if you had to do this for several values, such as all three load averages, it would grow tedious quickly. It certainly did for me.

So I added the concept of a fuzzy expect, or expecting an approximate value (in version 4.7.0). With fuzzy expect, you must declare how much fuzziness you will permit. This is similar to setting the timeout value (e.g. @10) or setting a custom prompt (e.g. */< /), it remains in effect until you change it or the script ends. To set the fuzziness use, the following:
~=1.5
Then to apply this fuzziness to the load problem above, all that is required is:
>uptime
<load average
~<(1.5)

That's it! What the last line means is: expect the first number after 'load average' and compare it with the tolerance set with the earlier fuzziness value. If the 1 minute load average is N, then N must fall in the range of 0 to 3 (1.5 + or - the fuzzy value of 1.5).

But wait, it could be simpler yet, since fuzzy expect can be combined with literal expect, like this:
>uptime
~<load average: (1.5)

The parens defines which part is fuzzy. Alas, expect-lite still has room for improvement, and it can only do one fuzzy expect per line. If you needed to check all three load averages, then you would have to add a couple more lines:
>uptime
~<load average: (1.5)
~<(1.5)
~<(1.5)

That certainly is easier than sucking those three load average values into dynamic variables, and then running three separate if statements to do the check.

Some additional qualities of the fuzzy expect are:
  • Default fuzzy value is 10, change it to what you would like
  • Regex can be used (just like with '<')
  • Just like '<', matches consume the expect buffer (see Consuming Output)
  • For those who dream in hexidecimal, fuzzy numbers can be in hex (e.g. 0xdead)
  • Fuzziness only applies to numbers
Sometimes life is fuzzy (and warm), now expect-lite can help with that cashmere sweater(s).

PS. you will remember from "Demystifying Regex" that \d is a digit.



Monday, February 17, 2014

Including (beer) made easy

Include Beer
Recently, I had a question from an expect-lite user about include files. Include files in their simplest form can be thought of an automated way to paste code into your script. And the original intent of an include file was just that. This concept is not a new in computer languages, c has header files, bash can source files, python imports files, etc. But as expect-lite has evolved, I'd like to say improved, and so have the uses of include files.

There are two types of include files,
  • standard or regular
  • fail script
The standard type of include file starts with a tilde followed by a file name.
~my_beer.inc

Using paths and automatic search for include files

There is an automatic search made by expect-lite to look for a simple file name (with no path defined) in the same directory as the expect-lite script, making it easy to use include files without having to worry about their location.

However, if you want to keep your include files in a different location, say a common directory, that is also supported, either by specifying a relative path:
 ~../common/my_beer.inc
Or by an absolute path:
 ~/Users/craig/common/my_beer.inc
This last one may look a little confusing, since bash uses the tilde to indicate the user's home directory. This is not a bash tilde, but an expect-lite tilde, representing "include this file from this location." I have a habit of putting the extent ".inc" on my include files, which just makes them easier to find and manage. But expect-lite doesn't care about extents, you can use anything that works for you.

A Simple Example

So great, now we know how to include something, what does it actually do. In the following example, the include file, my_beer.inc will be:
#my_beer.inc
$beer=beer
; $i bottles of $beer on the wall

And the main script (example 1):
#!/usr/bin/env expect-lite
# Count the beers backwards
$i=99
[ $i > 0
  ~my_beer.inc
  -$i
]

An include script can have any expect-lite lines, and shares global variable space with the main script. In the above example, $i is a counter variable in the while loop, but it also available in the include script which will print out the counter (using the semicolon printable comment feature).

Given this simple example, it would have been just as easy to paste the include file lines into the main script and skipped the include file. And that is a natural way to look at include files. But it would miss some of the power of include files.

Include files like functions

For example, if I modified the main script just a bit, you will see that just as the power of constants can be used on the main script when placing constants on the CLI, it can also be applied to include files.
Editing the main script (example 2):
#!/usr/bin/env expect-lite
# Count the beers backwards
$my_favourite=stout
$i=99
[ $i > 0
  ~my_beer.inc beer=$my_favourite
  -$i
]

By adding a parameter to the include file beer=$my_favourite it is possible change the behaviour of the include file which will now print:
99 bottles of stout on the wall

But wait, you say, include files share variable space of the main script, all I had to do was set $beer to stout in the main program, and this absolutely correct. But there are times when you may not want to change the value of $beer in the main script, passing values to the include script allows you to use include files more like functions than just copy and pasting lines into your script. Since expect-lite does not support real functions, this is a method to use when desiring function-like behaviour.

Fail Script

The other type of include file is that of a fail script. The expect-lite script has no knowledge of if a step failed or passed, this is managed by expect-lite itself. A failed step will either immediately stop the script (default behaviour), or continue to the end  of the script (when *NOFAIL is used).

The fail script is old feature which was designed for over night regression runs, where if a script failed, some clean up may be require before running the next test script. But it has more modern uses as you will see.

The fail script is declared, but no action is taken until script failure occurs (usually something expected was not returned). It is declared anywhere, usually early, in the script with the following:
*~my_fail.inc

There can only be one fail script active at a time, but the name can be changed as often as needed by declaring another fail script which will be in effect until the name is changed, or the end of the script:
*~my_broken_bottle.inc

In the following example, we determine what day it is, and it it isn't the weekend, call a fail script which sets a flag and returns to the main script (example 3):
#!/usr/bin/env expect-lite
# Check if it is the weekend, drink beer
*NOFAIL
*~no_beer.inc
$beer_days=Saturday|Sunday
>date +%A
<$beer_days
# use if statement to print correct beer drinking action
? $weekend==no? ;no beer today :: ;hoist a beer, it is $beer_days 

The include fail script, which will only be run if it is not a weekend looks like:
#no_beer.inc
# set flag if this script runs
$weekend=no

With these two scripts, there is no need to know if the result of the date command fails, since the fail script will set a flag (or variable) $weekend to signal to the if statement in the main script  to send the correct comment (the :: means else in the if statement).

Note the variable $beer_days=Saturday|Sunday includes the regex OR (a vertical line, also called a pipe) which means Saturday OR Sunday. Since the expect line <$beer_days is a regex evaluation, only the one word Saturday or Sunday need be found, and other days of the week will trigger the fail script. More on regex can be found at Dymystify Regex with 7 simple terms (including OR).

Of course the real power of include scripts come from reuse. For example, create a single telnet login include script, and every time a telnet is required, just call the include script. If later, you want to know what other users are on the system at the time of the login, just add a >w command to the telnet login script, and now everywhere that include script is used, it will automagically list the users and what they are doing.

Including Beer

Include scripts are there to make your life easier by simple copy/paste, function calls, fail scripts, and code reuse. Automating beer drinking, include me in.

EDIT: as of version 4.7.0, include file names can not start with equals '=' or larger-than '<' as these are used for fuzzy expect


Monday, January 6, 2014

Moving Forward

Moving Forward
Much has happened in the past year, including 2,190 downloads of expect-lite in 2013. Thanks to comments and requests from users the following features have been added to expect-lite in 2013:
  •  Native Logging (saving output to a file) using the *LOG or *LOGAPPEND
  • Updated IF statements to apply Code Blocks to if/then and else blocks
  • Foreach loops using Code Blocks
  • String Math, permitting search/replace, concat, and remove strings
  • Experimental work on a web frontend, el_run.php, permitting scripts to be executed from a web browser
  • Lots of little bug fixes
With the advent of the new year, it is time to look forward, and continue to improve expect-lite always keeping in mind the mantra: keeping it simple. There are a few ideas under consideration for 2014 such as: range checking (e.g. 5 < $x < 10), adding environment variables such as EL_REMOTE_PORT (to allow telnet on a user defined port), and unsetting a variable (useful when using pseudo arrays).

Expect-lite is user driven software. I look forward to your comments, and continuing to improve expect-lite in 2014. Moving forward while keeping it automation for the rest of us.

Craig...