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