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


Wednesday, October 17, 2012

Consuming Output, eating tables for lunch

There are times when a table is presented, and you the script writer is interested in only part of the data. The traditional method of parsing tables is to figure out the rows and columns, and assign them to variables, and check the variables for the right values. But this is a lot of work, and not in keeping with expect-lite's credo, keeping it easy.

Take the following example, which can be done on any linux machine (in windows use just the route print command):
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.1.1.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 eth0
0.0.0.0         10.1.1.1        0.0.0.0         UG    100    0        0 eth0

Without turning this into a networking lesson, you can see there are rows and columns. You will note on the last row, second column is '10.1.1.1' which is the the default gateway for this machine.

Suppose you are interested in checking that the default gateway is correct, you could script the following:
>route -n
<10.1.1.1
And you would be done. You don't have to worry about which row or column the value of 10.1.1.1 is in, since expect-lite will search the entire 'route -n' output.

But suppose you wanted to capture the default gateway into a dynamic variable, and there was no guarantee that it would always be 10.1.1.1? Now things are a little more tricky, but not too much.
>route -n
<\n0.0.0.0
+$default_gateway=(\d+\.\d+\.\d+\.\d+)

Only took 3 lines, still didn't have to know about rows and columns. The second line, '<\n0.0.0.0' is using a very powerful feature of expect-lite, one I call consuming output.

When a command is sent in expect-lite, and text is returned (like the output of route -n), it is held as a big blob (a buffer) of text. With each '<' or '<<' command, it eats or consumes the blob of text when it finds a match. So in the above 3 line example, line #2 consumes all the text up to and including the 0.0.0.0 on the last line (of the route -n output). This means the first 4 lines of the table are gone, consumed. The very next IP address in the remaining text blob is '10.1.1.1', which is what is assigned to the dynamic variable $default_gateway.

If you want to see text blob consumption at work, run the following:
*DEBUG
>route -n
<0.0.0.0
<0.0.0.0
<0.0.0.0

The output will look a bit chatty, but you will see the text blob (of route -n output) being gobbled by each '0.0.0.0'. I won't copy and paste the whole output here, but to give you and idea, it will look similar to this (abbreviated):
>route -n
...
find<<0.0.0.0>>
  in<<route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.1.1.0        0.0.0.0>>

The first 4 and a half lines
(including the 'route -n' line) are consumed

find<<0.0.0.0>>
  in<<         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0>>

The next half and a line are consumed

find<<0.0.0.0>>
  in<<         255.255.0.0     U     1000   0        0 eth0
0.0.0.0>>

The last chunk is consumed, leaving the next thing in the text blob, the default gateway. Each time text is found in the text blob (0.0.0.0 in this example), the top of the text blob is consumed, and no longer available to be found (or expected).

Using consumption can be useful in using the not-expect feature as well. As it says in the documentation, how long does one wait for something to not appear? With expect-lite it is about 100ms, or about 1/10th of a second. But what if the unexpected item comes after 1/10th of a second? You can use consumption.

Suppose you never want to see the default gateway of 192.168.1.1. with the above example and using the not-expect feature, you could script the following:
>route -n
<\n0.0.0.0
-<192.168.1.1
It will only not-expect 192.168.1.1 after it consumes the text blob down to the last line of output.

Another problem with tables is finding the right value, when multiple instances are in the table, such as 0.0.0.0 in route -n output. I use consumption to bookend the value I want. For example, if I want to check that the second line (the one starting with 169.254.0.0) gateway was correct, I could use consumption to find something before and after the desired value, such as:
>route -n
<169.254.0.0
<0.0.0.0
<255.255.0.0

Without having to know which row or column these items are on, it is possible to expect 0.0.0.0 is the gateway for 169.254.0.0. Bookending and consuming output ensures that the correct 0.0.0.0 will be matched.

So go, and fear not ye tables with rows and columns, for expect-lite is here to slay and consume them, making automation quick and easy.

PS. you will remember from "Demystifying Regex" that \n is a new line.
PPS. one could create a "simpler" regex to capture the default gateway, but then you would have to know about character classes, such as:
+$default_gateway=([0-9.]+)
PPPS. Think of a shelf with books, old school but still can be found in the library. The things holding up the books at the end of the shelf are called bookends.

Friday, September 14, 2012

Demystify Regex with 7 simple terms

Regular Expressions aren't for everyone. Regex is a powerful cryptic conflagration of characters which mean something, if only you could figure out what.

But what if there were a handful of regex terms which did 90% of what you needed. Then you could harness the immense power of regex, without having to learn a whole new language. After all, expect-lite is about making it easy.

Regex Whats

Regex is made up of two parts, what to look for, and does that "thing" repeat. The whats are characters which describe a number, a letter or a non-printing character (such as tab). The four terms you will want to know start with a back-slash and are followed by a single letter:
\d  is a number
\w  is a letter
\n  is a new line (think of it as a carriage return)
\t  is a tab

Regex Repeats

Repeats are useful for finding a string of numbers, for example 123456.  Two terms for the repeats are:
*  repeats 0 or more times
+  repeats 1 or more times

Regex matching with expect-lite

With these six terms you can create very useful regex expressions. The following example shows the output of the route command:
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.1.1.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 eth0
0.0.0.0         10.1.1.1        0.0.0.0         UG    100    0        0 eth0

You could use the following to match the interface:
>route -n
<eth\d

You could also match the first IP address:
>route -n
<\d+.\d+.\d+.\d+

Using the plus, +, means regex will match any digit repeating 1 or more times. An IP addresses can have 1 to 3 digits per octet (the number between the dots). The repeat makes it easy to match a variable number of digits.

Or even more useful would be to use a dynamic variable to grab the default gateway:
>route -n
<\n0.0.0.0
+$default_gw=(\d+.\d+.\d+.\d+)
 
By using the new line, the expect line will only match the 0.0.0.0 at the beginning of the line. I'll write more later about how to leverage expect-lite's capture buffer, but in this example, the <\n0.0.0.0 positions expect-lite to capture into a dynamic variable the very next thing that matches the regex \d+.\d+.\d+.\d+ which in this example the value of $default_gw would be 10.1.1.1

The regex OR

The seventh term of regex that is good to know is the OR command which is the vertical line or pipe, |
The pipe allows you to make a statement, such as mach this OR that. In expect-lite it would look like:
>echo "this"
<this|that

The above example is a bit contrived, but it is common to find the output of a command which might be true OR false, or enabled OR disabled, or UP or DOWN. This may be less useful for a simple match, but very useful in capturing a dynamic variable. The following command shows the interface state on a linux machine:
$ ip link show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:30:65:96:b5:4a brd ff:ff:ff:ff:ff:ff


To capture that state of the interface (UP or DOWN) in expect-lite, it would as simple as:
>ip link show eth0
+$intf_state=(UP|DOWN)

By using a simple, yet powerful regex, it is easy to capture the states of the interface in the example above.

The power of 7

I have only scratched the surface of Regex here, but it should cover 90+ percent of what you might need.  The 7 simple regex terms here; the whats, the repeats, and OR, it is possible to match just about anything you need in expect-lite.



PS. The above is not entirely correct, as the dot, is also a regex expression, but the above examples will work without having to know this eighth term.