Sunday, October 22, 2017

More than a shiny Prompt

Shiny Prompt
Recently, I have been assisting expect-lite script writers with their scripts, and the underlying problem was prompts. Prompt detection is very important in automating interactive programs. Fortunately, expect-lite allows for custom prompts (called user-defined prompts), but they can sometimes be tricky to define.

How do prompts work?

We use prompts without thinking what they really mean. A prompt is a way for the computer (or shell) to is printed to the screen when the current command is finished, usually a $, %, >, or #. It is the heart of an interactive program which is asking for user input.

With expect-lite, the prompt indicates that the computer is ready to receive the next command. Much work has gone into expect-lite to detect the standard shell prompts. But what if you are automating a install script where the prompt is something like '(yes/no)? '*. How does one automate that?

Two solutions


  1. Use a user-defined prompt with the */myprompt / syntax.  It is defined using regex and any characters in your prompt which are special to regex must be escaped with a backslash. Of the above yes/no prompt, the code would look like:
    */\(yes/no\)\? /
    >yes
     
  2. Use a non-regex expect line, followed by the answer with a no-wait-for-prompt send
    <<yes/no
    >>yes
While using the user-defined prompt is a more elegant solution, however it can be difficult to define the prompt without having a good knowledge of regex. The nice aspect of the second solution is although a bit wordy, no knowledge of regex is required. One only remember that the double >> means "do not wait for prompt".


Interaction of expect lines << and prompts

expect-lite is always searching forward when looking for an expect line. Take the following example:
$ ssh 6palolo
The authenticity of host '6palolo (2001:470:ebbd:0:221:2aff:fec3:6ab0)' can't be established.
RSA key fingerprint is SHA256:tFStOlPO2dKJyqGE6D+7C8b1xu/4.

Are you sure you want to continue connecting (yes/no)?
yes 

And the following script:
*/\(yes/no\)\? /
>ssh 6pallolo
<<continue connecting (yes/no)
>yes

This script will always fail. The expect-lite search buffer is NOT line oriented. It is just searching a blob of text including new-lines. The reason the script will fail is that the third line, an expect line,  will consume the text '(yes/no) ', and when the script continues and looks for the user-defined prompt it will no longer be in the search buffer, and the script will fail.

A better way to write the script is to not expect what the user-defined prompt will look for:
*/\(yes/no\)\? /
>ssh 6pallolo
<continue connecting
>yes

Lastly, method 2 can be used which does not require a user-defined prompt. expect-lite will wait for the text continue connecting before it sends yes.
>ssh 6pallolo
<<continue connecting
>>yes


Creating consistent reproducible scripts

In the end, you want to write a readable script which runs in a reproducible manner. Many times using method 2 (without user-defined prompts) is the easier path. But expect-lite allows you to create your own prompt detection, making your life easier, because expect-lite is about automation for for the rest of us.

* ssh will ask about continuing a connection to a new host by prompting with (yes/no)? 
** Reminder that << means expect without regex interpretation, and >> means send without waiting for prompt

Thursday, February 23, 2017

Mature Code, not a bad thing

There is a certain attractiveness to shiny and new things. That can apply to programming languages, which is why we see newer languages like ruby, lua, and swift. They have got to be good, after all they are new and shiny.

But there is the other side of the coin, stable mature code which doesn't change or break year after year. I chalk this principle up to why we still see code in c, a venerable language which is just a couple years short of turning 50.

Stable Code

Certainly, expect-lite is no c, but it is now twelve (12) years old, and I have always paid attention to making it backward compatible from its early days. The idea is not to break your code. One can spend way too much time troubleshooting broken code, that turns out the underlying infrastructure is what is broken your code.

RPM no longer working

Unfortunately, not everyone pays close attention to backward compatibility. It was recently pointed out to me that the expect-lite rpm would not install on RHEL 7 machines. It installed fine on the previous version, but something changed.

To be fair, I wasn't creating the RPM the Redhat way. I don't run a Redhat machine these days, having migrated to Debian/Ubuntu years ago. I was using a program called alien to create the RPM from a 'deb' package file. And something was no longer working.

A new RPM

Fortunately, one of the expect-lite users, volunteered to make the RPM on RHEL 7, and that has been published to the SourceForge site. If you use RHEL or Centos, you should run over and grab the new-working RPM from SourceForge.

Thanks to the expect-lite community for making expect-lite automation for the rest of us.

* rpm logo creative commons

Saturday, November 12, 2016

Automating small tasks


Doughnut Script
In life we do small tasks over and over, thinking, this is too small to automate. After all, it may take 10 minutes to automate it, and it only takes 10 seconds to do it.

Recently, I have been doing some testing on systemd, software for Linux. And I have been logging into the a DUT (Device Under Test) on a console cable. Using the console allows one to bring up and down the network interfaces, and still remain connected to the DUT.

It didn't take long to log in via the console, start screen, log into the DUT, then su to root, about 10 seconds total. Then, type the commands to run the test, log out, and quit screen.

I have been doing this for a couple of months, when I thought, hey, maybe I should sit down and automate this. Not because I was tired of logging in manually, but because I wanted to write a larger regression script for bugs I had raised against systemd.

Creating a doughnut script

So I spent 10 minutes writing and debugging a console login doughnut script*. Then I copied the script, and started adding my regression lines (in the middle). Since this is systemd, the regression test doesn't ever seem to pass, but at least I can quickly determine what is broken.

Because I am never sure the state of the DUT, the serial login script does prompt detection to determine the next step in logging in. It does this with:
# set user prompt
*/.*: /
# detect prompt
>>^M
>
+$prompt1=\n.*(\$ |login:|word:)

It determines prompts they same way you would with your eyes, presses <return> and "sees" what is returned. If it returns a prompt ($), then we are already logged in. If it asks for a login, then the script logs in.

Code Reuse

By setting the actual serial login in an include file, it is easily added to any script going forward. Suddenly the 10 minute time investment is paying off. But save yourself 10 minutes, and get the scripts from my github examples directory (look for serial_login.elt, serial_con.inc, and serial_discon.inc).

I find myself using the serial console script all the time, and it takes less than a second to log in. Automating little repetitive tasks can make life easier and faster, truly automation for the rest of us.

* a doughnut script is a script that sets things up, pauses (with *INTERACT), then breaks things down and cleans up.
** doughnut image by Evan-Amos - Own work, Public Domain, Link

Thursday, January 7, 2016

Demystifying Regex Redux with 9 simple terms

Can I have your number?
When I wrote demystifying regex with 7 simple terms a while ago, I left out a couple really useful regex terms. So I guess this would have to be re-written as 9 Simple Terms.

Regex is one of those things that when you need it, you need it. But it is from the 80's and cryptic. Most regex expressions you see are too complex, and hard to follow. In this post, I'll show you a couple more terms to help you keep it simple and to a minimum, while allowing you to tap the power of regex.

expect-lite has very good support for regex meta characters (the ones that start with a backslash "\"). As a quick review of the 7 terms, there are:
  • Repeats: * and +
  • Meta characters: \d, \w, \n, \t
  • Or: |
But there are a couple of regex meta characters which I have found useful in addition to the 7 above, when skipping over some columnar info to get that column you want to validate (or capture into a variable).
\s is whitespace (space, tab, or newline)
\S is not whitespace

Working with the example from demystifying regex with 7 simple terms:
$ 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 match the first IP address:
>route -n
<\d+.\d+.\d+.\d+ 


But what if you wanted to match the default route metric (on the last line) , rather than the first IP address, you could use (I have highlighted the value we are searching for in bold):
#validate metric
>route -n
<0.0.0.0\s+\d+.\d+.\d+.\d+\s+0.0.0.0\s+UG\s+100\s+\d\s+\d\s+eth0

This introduces a new meta character, \s which is a space (or any white space). But it starts to look complex. Keeping it simple, use the non-space \S, and back tracking from a known point (in this example 'eth0') will simplify the regex some:
#validate metric
>route -n
<100\s+\S+\s+\S+\s+eth0

It is better, but could be simpler by keying off of the flags column.
#validate metric of default route
>route -n
<UG\s+100.+eth0

This uses the space meta-character, \s, and uses another meta-character mentioned in the fine print of the original post, the dot, or '.' which matches any character. It is good to use the dot sparingly, as it can often match more than you would expect. But in this example, because there is only one default route (it is after all, only IPv4*), and it is always on the last line, it is pretty safe to use the dot.

To see just what expect-lite did match, use the *EXP_INFO directive on the CLI or earlier in your script.

Regex Guidelines

It is a good idea to keep regex as simple as possible. As we saw above, it is easy to create complex regex, but that leads to challenges in maintaining code later. Every time someone has to debug the script, they have to figure out what the regex is doing. Shorter, simpler regex will always win out.

Regex has the concept of anchors (^,$), but I haven't included them the 9 simple terms because of a couple of reasons:

  • Anchors don't work as you would expect in expect-lite. One would expect that you could use an anchor at the beginning of a line, but expect-lite doesn't evaluate output on a line by line basis, but rather a blob of text which includes new-lines. Therefore, if you need to "anchor" your regex, do something like '\n169.254.0.0' Regexs with anchors tend to not be simple or short
  • I have seen regexs where the entire line is described, from beginning of the line to the end of the line, with anchors at each end. This almost always makes a very complex and brittle regex. A change in the column width, can break these kinds of regexes. Rather, it is much easier, and less code intensive to do a sparse validation of output using simple regexes (as shown in the example above).


Not everyone is a regex expert. Plan on helping the next person who looks at your script by writing a comment about what the regex is doing. And if you are lucky enough to be the next person to look at your code, then you will be thankful that you wrote your future-self a note.

Recap the 9 terms

To recap, and give you a single place to look for a reference, the 9 terms are the single character meta-characters:

  • \d  is a number
  • \w  is a letter
  • \n  is a new line (think of it as a carriage return)
  • \t  is a tab
  • \s is white space (including \t and \n)
  • \S is a non-space (any letter, number, symbol)
  • . is any character (use this sparingly**)


And the repeat characters which are modifiers to the terms above:

  • *  repeats 0 or more times
  • +  repeats 1 or more times


And the regex OR term, |

The power of 9

You can still use only the original 7 regex terms and accomplish 90% of what you need. The additional 2 meta-characters just give you a bit more control over matching. And for those of us with a finite memory, it is still fewer than the fingers on two hands.


* IPv6 can often have multiple default routes, and the metric becomes very important in determining which one is used.
** the regex dot is extra credit



Tuesday, September 15, 2015

Sleeping while you work

Sleeping: restful, relaxing, restorative. But sleeping in a computer script is pausing the script for a specified amount of time. As of version 4.9.0 a native sleep command, using a colon, has been added.
Counting the seconds

"Why wait until expect-lite has been around for 10 years before adding sleep?" you may ask. Because I have seen sleep abused in other scripting languages, usually a scripter will add a 60 to 600 second sleep rather than check for the event with a polling loop.

Polling with a sleep

But polling loops are an excellent example of where to use a sleep. It is usually unnecessary to check for a state change (ethernet interface up, for example) on a mili-second time basis. More likely if the interface comes up in a couple of seconds, that is good enough. The sleep will slow down the polling loop so the loop does not put an undo load on the machine.

# using a polling loop to check when eth0 is up
$intf=eth0
$int_state=none
[ $int_state != UP
    ip link show dev $intf
    +$int_state=(UP|DOWN)
    # sleep 2 seconds to slow down loop
    :2
]

Wait a sec :01

The colon ":" indicates a sleep. In the above example :2 is used to sleep (or pause) the script for 2 seconds. Sleep is always in seconds, but mili-seconds are also supported such as 5 mili-seconds:
    :0.005

The native sleep (using the colon) also gives indication that the script is sleeping. There is nothing more frustrating that debugging a script and wondering is it hung or is it sleeping. expect-lite will print dots to show the progress of the sleep. A 12 second sleep would output:
Sleeping: 12 
....+....10.. 

Each dot represents a second, with the plus every 5 seconds, and a number every 10 seconds. This output will go to stdout, and also be logged to a file with the *LOG command so that it can be observed later. The output can be disabled with the *NOINFO command if you want less clutter.

Transparency in sleeping, making scripting easier

A goal of expect-lite is transparency, showing you what it is doing, to help you debug script errors, or determine actual problems with the device you are testing. And now, you can sleep on it.


Sunday, January 11, 2015

Looking through the window, the expect-lite debugger

Looking through the debugger
In January 2015, expect-lite will have been making automation easier for 10 years. In celebration of that event, version 4.8.0, includes improvements to the debugger to make automation even easier.

expect-lite has had an integrated debugger,  called with *INTERACT, since version 4.0 (Oct 2010).The expect-lite debugger allows you to:
  • all the standard debugger stuff: step, skip, view variables
  • type commands directly to the device/host the script is connected to
  • execute arbitrary lines of expect-lite script (copy/paste uses this as well)
The debugger has the standard things you would look for in a debugger. Pressing escape h will print the debugger help:

IDE: Help
  Key          Action
  ----        ------
  <esc>s      Step
  <esc>k      sKip next step
  <esc>c      Continue
  <esc>v      show Vars
  <esc>e      show Env
  <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

The debugger is like a window to the device being automated. Once in a breakpoint, the debugger silently steps aside, and allows you to type directly to the device. Perhaps you aren't getting the response you expected, or the device wasn't configured as you had expected. You can fix it while in the middle of your script.

The debugger silently watches what you type, and decides if the text is for the device, or is it an expect-lite command it must execute. How does it do this? Mostly pretty well.

Typing expect-lite commands in the debugger

You may have noticed that expect-lite commands start with a punctuation character like '>', '<', '?', '@', or ';'. The debugger watches for these characters at the beginning of a line. But there are some tweaks to prevent the debugger from accidentally grabbing a line that was intended for the device. These are limitations only when typing in the debugger, and are not required when executing the expect-lite script.
  • >send this    There must be no space between > and the text to be sent
  • ; comment   There must be a space between the semi-colon and the comment
  • ?if condition...   When using an IF statement in the debugger then the optional 'if' must be used, there are just too many question marks in normal text
  • $var=value  Variable assignments must have an equals sign (no spaces)
  • no white space before the expect-lite command (punctuation)
I wrote in an earlier blog entry (writing scripts with copy and paste), that using the debugger you can also copy/paste into a running script. When copy/pasting into the debugger, only the first line need follow the above limitations, the remaining lines can have leading white space, etc, since the debugger has decided that the entire paste is expect-lite script.

*SHOW ENV

The biggest improvement to the debugger in version 4.8.0,  is *SHOW ENV. Similar to *SHOW VARS, the expect-lite environment consists of a list of directives which are enabled/disabled, such as *INFO, and *TIMESTAMP, as well as ones with values like user defined prompt, and the infinite loop count (*INFINITELOOP). Lastly, *SHOW ENV will display any shell environment variables which begin with EL_.

$ DEBUG Info: Printing expect-lite directives/environment
Environment:          Value:
CURRENT_FORK_SESSION  default
DEBUG                 off
DVPROMPT              on
EOLS                  LF
EXP_INFO              on
FAIL_SCRIPT           fail_test.inc
INFINTE_LOOP_COUNT    5000
INFO                  on
LOG                   off
LOG_EXT               .log
NOFAIL                2
NOINCLUDE             off
NOINTERACT            off
REMOTE_SHELL          none
TIMESTAMP             off
USER_DEFINED_PROMPT   .*$ $
WARN                  on
fuzzy_range           10
timeout               2
EL_CONNECT_METHOD     ssh_key
EL_REMOTE_HOST        localhost

Of course, if you are in the debugger, you don't have to type *SHOW ENV, you can just type <esc>e.

Just as you can start a new shell, by typing 'bash' or 'csh' at the prompt. There is an included example called 'el_shell.elt' which you can use to create an expect-lite shell. It is a simple script which just drops you into the debugger. From there, you can type regular linux commands (or cygwin linux-like commands), expect-lite commands, or explore the debugger help with <esc>h.

Doughnut Scripts

I find I often create a doughnut script, a script with a hole in the middle. I use this to have a script set up my environment, then drop to the debugger, allowing me to plunk around, then when I am done, I exit the debugger (with '+++'), and the script cleans up. I call this automation assist. It doesn't do everything, but it allows me to explore/test faster than doing it all by hand.

The debugger command detection isn't perfect, but it works pretty well. Sometimes you may find a smudge or a dirty spot on the debugger window, but hopefully, it will be clear enough to see that expect-lite is automation for the rest of us.

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!