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.


Tuesday, February 19, 2013

To quote or not to quote

Quotes, really useful to convey a meaning or sentiment. When used in traditional software languages, quotes depict a string of characters. However, the mantra of expect-lite is keeping it simple, and quotes are not required.

In fact, quotes aren't even optional, and can cause problems when trying to quote strings. Take the following example:
$abc="hello world"
$def=hello world
The two variables are NOT equal. Expect-lite will store the quote characters as part of $abc.

Traditionally using quotes allows the programmer to encompass all characters between the quotes, but this becomes a problem when the quote character is part of the string to be stored. But then a whole system of escaping characters needs to be introduced.

Expect-lite eliminates all that complexity by storing everything after the equal sign, spaces, quotes, tabs, everything. Even more equals signs, such as below:
$equation=5 + 2 = 7

This feature is handy when assigning non-printable characters such as tab. It is possible to store the tab character by pressing the tab key after the equals sign:
$tab=

The $tab can then be used to test tab completion of a command line, such as when using the bash shell, and pressing tab twice to see possible file names:
>ls /etc/rc$tab$tab
<rc3.d

Quotes are not even required when using while loops or if statements. Expect-lite will just do the right thing. For example earlier we set the variable $def, and the following will totally work:
?if $def == hello world ? >hello back!

So save the quotes for Shakespeare, and remember expect-lite is about keeping it it simple.




Sunday, January 20, 2013

Turning Eight

Turning Eight
It started on 19 January 2005. Hard to believe it has been eight years since I first took up the keyboard and started writing expect-lite.

Of course, it was a few weeks before expect-lite could actually do anything useful. But the idea of keeping it simple, and sending > and expecting < was there.

In software development, as a rule, major versions don't have to be compatible with previous versions, but expect-lite has kept backward compatibility since those early days of 2005. It has been a long-standing goal to be able to run a script written years ago on the latest version of expect-lite.
  • 1.x - Basic functionality
  • 2.x - Added Dynamic Variables, Better Var parsing, Embedded Expect
    • version 2.4.0, expect-lite goes open source!
  • 3.x - Looping using %labels,  multiple sessions (*FORK), break points (*INTERACT), Colour
  • 4.x - Integrated Debugger with ^\, Integrated as a TCL library, Code Blocks [ ], User defined script help using ;;; and Logging to a file (*LOG)
Fast forward 8 years, and almost 10,000 downloads later, and expect-lite is still going strong. Many of the features added are a direct result of users' comments. For example, built in logging to a file using *LOG, has has been on the request list for a while, but I do listen, and now it is in the latest release.

Thank you for all of those comments and feature requests over the years, I look forward to more.

Craig...
[author and maintainer of expect-lite]








Sunday, December 9, 2012

Writing scripts with copy and paste

Sure everyone who has tried to use expect-lite knows it is easy to use if you are just using > and < in the script. But what if you want to do something a bit more tricky, say capture a value from the screen, and make a comparison.

It is a bit contrived, but say for example, you want to check the number of packets received on the eth0 interface is below 100,000 (100 thousand). In expect-lite, it is quite easy to do the comparison using a dynamic variable, a variable which is assigned based on the output of a command at run time. Sometimes it can be tricky to get the regex right to capture the info you want, but I will keep regex simple, and use copy and paste to solve this puzzle.

Setting up the environment


Using el_shell.elt (in the examples directory) makes it easy to try out expect-lite lines using copy and paste between two windows (an editor and terminal window). The first thing you want to do is setup two windows like this:
two window setup
I prefer overlapping windows, but it is up to your preference. The key is that you start an editor (I use nedit) and a terminal (or xterm) and can see them both at the same time. In this exercise, we will write lines of expect-lite code in the editor, copy them, then paste them into the terminal which is running el_shell.elt. Having overlapping windows (rather than full screen) makes it easier to see what is going on.

Now that you have your editor started, go ahead and start el_shell.elt in the terminal by typing:
./el_shell.elt

el_shell.elt is pretty simple, it starts expect-lite, then does an *INTERACT to drop you into the debugger. It may look like you are just at the bash prompt. To confirm you are in the debugger, press ESC then h (in cygwin, press back-quote then h). You should see the following output:
IDE: Help
  Key          Action
  ----        ------
  <esc>s      Step
  <esc>k      sKip next step
  <esc>c      Continue
  <esc>v      show Vars
  <esc>0to9   Show N next lines of script
  <esc>-1to-9 Show N previous lines of script
  ctrl+D      Quit & Exit expect-lite
  <esc>h      this Help

[cvmiller@fedora-arm:~]$


Using el_shell.elt


Now that you have the environment setup, let's capture the interface RX counter. The ifconfig command will display interface counters (and other useful info). We use a simple regex, any digit,  \d, with the modifier of plus, for one or more (see Demystify Regex with 7 simple terms). This will match a contiguous run of numbers. In the editor type the following lines:
>ifconfig eth0
+$eth_rx=(\d+)

Select the 2 lines, copy, then switch to the terminal window, and paste the lines, the terminal output should look something like this:
[cvmiller@fedora-arm:~]$ ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.1.41  netmask 255.255.255.0  broadcast 10.1.1.255
        inet6 2001:470:1d:583:246:b3ff:fe05:cbe5  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::246:b3ff:fe05:cbe5  prefixlen 64  scopeid 0x20<link>
        ether 00:46:b3:05:cb:e5  txqueuelen 1000  (Ethernet)
        RX packets 114320  bytes 24060958 (22.9 MiB)
        RX errors 2  dropped 80  overruns 0  frame 1
        TX packets 20022  bytes 4036177 (3.8 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[cvmiller@fedora-arm:~]$
Assigned Var:eth_rx=0


You can see that the value assigned to $eth_rx is 0 (zero). Not quite what we were looking for. Using dynamic variable assignment (aka capture), only the part inside the parenthesis will be assigned. Let's switch back to the editor and refine the dynamic assignment statement a bit more:
>ifconfig eth0
+$eth_rx=packets (\d+)

Copy and paste (into the terminal) again. Now we see at the bottom of the terminal (removing the ifconfig output for brevity):
[cvmiller@fedora-arm:~]$
Assigned Var:eth_rx=
114320

That is looking better. Let's make the script a bit more versatile by using a static variable for the interface, which could later be overridden with a constant (see The Power of Constants). And suppose we want to ensure that we always assign the RX packets, we could tweak the dynamic assignment statement a bit more in the editor:
$iface=eth0
>ifconfig $iface
+$eth_rx=RX packets (\d+)

Copy and paste (into the terminal) again. With confidence, we see at the bottom of the terminal:
[cvmiller@fedora-arm:~]$
Assigned Var:eth_rx=
114320

Now we have a RX packet value we can compare, so let's update the script in the editor a bit more, and another copy and paste into the terminal:
$rx_limit=100000
$iface=eth0
>ifconfig $iface
+$eth_rx=RX packets (\d+)
?if $eth_rx > $rx_limit? [
    ;red RX packets over limit
    *FAIL


The result now looks like: 
Assigned Var:eth_rx=114320

If: expr { "114320" > "100000" } |then   [|result=TRUE

 RX packets over limit

expect-lite directive: *FAIL

Even though the scriptlet failed, el_shell.elt is still running. This is because while in the debugger, one has immunity from failing. After all, it would not be helpful if the debugger quit just because you pasted in some code that failed.

Copy and Paste, a simple technique to create complex solutions


As you can see copying and pasting into the debugger allows you to run arbitrary expect-lite code, and see the results instantly. The debugger is not limited to simple commands, you can paste while loops, directives (like *DEBUG), include files, or other complex code and have it execute right away.

The mantra of expect-lite is keeping it simple. What could be simpler than copy and pasting scriptlets while you automate complex solutions.

PS. to simplify things, I have artificially kept the RX packet counter constant, in real life, it will probably increment
PPS. In Cygwin, you will have use ipconfig rather than ifconfig
PPPS. In Linux/Unix/Mac you need not actually copy and paste, since X has an automatic clipboard, by just selecting the text in the editor, moving the mouse to the terminal and pressing the middle button on the mouse (which will paste the selected text). This makes the overlapping window setup very fast.
PPPPS. the debugger does have a restriction on copy and paste, the first line must not be indented (lines below the first line can be indented).
PPPPPS. el_shell.elt was added in the examples directory in release 4.3.3