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

Sunday, September 15, 2013

Code Blocks, adding structure to your script

 
Cold Block, I mean Code Blocks
For eight long years, expect-lite was a simple scripting language. Running scripts from the top to the bottom of the file. At the request of an expect-lite user, code blocks were introduced in version 4.2. Code Blocks permit grouping of lines and add structure to your script.

Code Blocks allow you to create multi-line conditionals (if statements), while and foreach loops. Naturally, Code Blocks can be nested. Code blocks start with a square bracket, [, and end with a closing square bracket, ]. Indentation within a Code Block is not required, but makes the script more readable. Let's look at each of these in more detail.

Multi-line Conditional

It has been a limitation for a long time, that expect-lite commands had to be on one line. So the conditional was limited to the following, with the double colon representing else
?if $var > $big ? >do something :: >do something else

However with code blocks, it is now possible to insert multiple lines for the then and else statement, such as:
 ?if $var > $big ? [
  ;red --- Var:$var is out of bounds
  *FAIL
]::[
  ; === incrementing Var:$var
  +$var
]
In the above example, if $var is larger than $big, there is something wrong, tell the user about it using a red coloured comment line, and then mark the script as failed.

The While Loop

The While loop is a simple looping mechanism which has a test or comparison at the top of the loop, and therefore usually needs a couple lines of setup before hand.
$i=0
$max=10
[ $i < $max
   >echo $i
   +$i
]
In the above example, a loop counter variable, $i, is set to zero, and a maximum count, $max, is set before the while loop begins. As mentioned earlier, the code block begins with the open square bracket, and the while loop test is defined, $i < $max. The indented lines print out the value of $i as the while loop progresses, and +$i increments the value of $i. Lastly, the lone close square bracket on a separate line, indicates the end of the Code Block, and hence, the while loop.

The Foreach Loop

The foreach loop is another looping mechanism where the items to be iterated over are known beforehand. For example, the script writer may want to iterate over the planet names. In each iteration of the loop, the variable $planet will be assigned a value in the list of planets.
[ $planet=mercury venus earth mars  jupiter  saturn  uranus    neptune
  >ping6 c1 $planet
] 
Of course, until the internet is extended to the interplanetary-internet, using IPv6 naturally, you may not receive an answer to the ping, but you get the idea. 

Foreach loops have the limitation in expect-lite, that the items in the list must be space delimited. As you can see in the example above, multiple spaces are allowed. And if your list as something else as a delimiter, such as a comma, there is an expect-lite feature called string math to help.
$planets=mercury,venus,earth,mars,jupiter,saturn,uranus,neptune
; === use string math /search/replace/ to convert commas to spaces
=$planets /,/ / 

 Please look at the man page, or online documentation for more information on string math.

Keeping it Simple

Code Blocks do add a bit of complexity to the script, but the added structure will bring more power, and clarity to your scripts, all the while keeping it simple.




Saturday, July 6, 2013

Just like being there

Remote control was what expect-lite was born to do. In the early days of expect-lite, there was a need to log into an embedded linux machine and run some tests. There was also a need to log into different remote hosts with same script, so switching which remote host to test had to be easy, as easy as putting it on the command line.

Of course there are several different methods to access a remote host, and to that end, expect-lite supports telnet, ssh, both will username and password options, and a third method, ssh with keys, which requires no password, but the keys have to be set up ahead of time. These methods are selected by setting an environment variable EL_CONNECT_METHOD.

Over the years of using expect-lite I have found while handy to connect to the remote embedded system, it isn't always the best method. Often embedded systems don't have a full linux (or other OS) on them, after all it is by its nature a small system. So I have found that logging into my desktop linux system (aka localhost) provides a rich full set of utilities, and the script can then log into the embedded system (using a variable $IP to control which embedded system).
./myscript IP=192.168.1.10

My .expect-literc which creates my environment  looks like this:
export EL_REMOTE_HOST=localhost
export EL_CONNECT_METHOD=ssh_key


Of course, I have set up ssh keys to my localhost, allowing expect-lite to login without using a password. This is easily done by using the configuration-only option of the installation script, install.sh, in /usr/share/doc/expect-lite (if you installed using the deb or rpm file), or in expect-lite.proj directory where ever you untarred the tarball.
$ cd <install.sh directory>
$ ./install.sh -c
=======================================
Installing expect-lite version "4.6.0"
1,2,3)  ===================
        Configuration Only selected, skipping install steps 1,2,3
...


Fine print: the install script really only sets up the environment for bash shell (or sh, or ksh). It does not set up csh (or similar shells). I use bash, and that is the default shell for most distros today. expect-lite can and does work with csh, but you will need to adjust the environment using csh syntax

Before using the environment created by install.sh it is necessary to either a) relogin, or b) source your ~/.bashrc from the shell. Only then will the new environment take effect.

With this environment, expect-lite will log into the localhost using ssh keys (yellow arrow in diagram above), and then the script will log into the remote host, such as an embedded system (the blue arrow). For additional flexibility I use the *FORK command to create another session to the localhost linux system.
*FORK linux
And switching back to the remote host with:
*FORK default
Now the script has access to the rich set of linux utilities, and the embedded system under test.

Of course, you don't have to use this method. expect-lite supports connecting directly to the remote host as I stated earlier. Either way works well, and for the script, it is just like being there.