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