This package provides a simple Scala implementation for Expect, http://expect.sourceforge.net. More
specifically it provides an implementation of the
For a throrough description of Expect and complete documentation, see http://expect.sourceforge.net
The objective is to programmatically interact with an external process inside Scala code. This makes it possible to use programs or scripts that are available on the host OS. The most common example is to use a shell script to perform some computations and collect the results. For instance, one may want to use
ls to collect a list of files in a directory, or
ps to determine which processes are currently running.
In the Expect world, this is done by spawning a process (that runs
ls), and then interacting with it via the
Send and Expect
The main problem to address is to detect that the output is ready to collect.
This means we need to be able to determine when the external process has finished its computation on a given input.
By convention in Expect, the termination of a computation of the external process is signaled by the a sentinelle token: the prompt. appears in the output channel.
Overall, a typical interaction is 1) sending data to the process, and can be done using the input channel (e.g.
stdin) and 2) waiting for a string terminated by the prompt to appear on the output channel (e.g.
The Expect package provides an interface to achieve this via the
expect methods. The only assumption is that the process' responses are postfixed with the prompt.
To interact more than one time, we can send a command, collect the ouput which is a string followed by an expected prompt, and start again.
The prompt can be defined by a simple regular expression. A typical interaction
with the external process is a sequence of
1. send a command to the process.
2. wait for a sequence of characters (including spaces/newlines) followed by the prompt.
The wait is implemented by the
expect command. This command has a timeout (duration), and if the prompt does not appear on the process
stout channel before the timeout has expired, the command returns a timeout failure.
If the prompt appears within the allowed time bound (set by the timeout), the string that preceedes the prompt is returned by
Notice that the
expect method terminates either when the prompt is seen on the output channel or when the timeout expires, but no later.
As the process we interact with may be non-terminating it is important to understand that the prompt may not be 'suffix-closed'. For
instance a prompt defined by the regular expression
bash>(\n), i.e. the string ''bash>'' followed by an arbitrary number of newlines may
result in a timeout even if the process outputs ''bash>'' within the time bound. Indeed the
expect command is waiting for the
longest string that can match the regular expression that defines the prompt. After receiving ''bash>'', the next character on the
output channel of the process could be a newline, and the
expect command will keep waiting for the next character to extend the prompt matching as much as it can.
A prompt like ''bash>'' will not exhibit the same problem: if a string followed by ''bash>'' appears on the process' output channel within the time bound, the expect command will terminate and succeed.
Another important thing to bear in mind is that processes and pipes are created to implement the communications between the Scala program
and the external process.
It is mandatory to release these resources at the end of the interaction otherwise your system may run out of resources (e.g. file descriptors). The
Expect class provides an
destroy method to release the resources. Used together with a proper Automatic-Resource-Management system, e.g. Scala-ARM, it provides a safe way for releasing resources, even when something unexpected happens in the Scala program (e.g. exception).
Add the following dependency in your
libraryDependencies += "org.bitbucket.franck44.expect" %% "expect-for-scala" % "1.0.0"
You may also want to build the current
expect-for-scala package (master branch of this repo) and in this case use the command:
This should compile, run the tests and publish the package
The simplest usage for the Expect object in the package is to create a process and then use send/expect commands to interact with it. The bash script terminal-sh.sh takes an input of the form cmd1::t1 cmd2::t2 ... cmdk:tk and outputs cmd1 after t1 seconds, cmd2 after t1 + t2 seconds and so on. After the last command cmdk it prints out the prompt "param" on the output stream.
scala> import org.bitbucket.franck44.expect.Expect import org.bitbucket.franck44.expect.Expect scala> import scala.util.matching.Regex import scala.util.matching.Regex scala> import scala.concurrent.duration._ import scala.concurrent.duration._ // create an Expect object with prompt "ready>" scala> val e = Expect("src/test/resources/test-terminal.sh", List("ready>")) e: org.bitbucket.franck44.expect.Expect = Expect(src/test/resources/test-terminal.sh,List(ready>)) // expect a prompt ">" scala> e.send("read::2\n") ; e.expect(""">""".r, 3.seconds) res0: scala.util.Try[String] = Success(read ready)
A timeout may occur if the process does not send the response within the time bound. Notice that if the process is killed (or crashes) and the input/output streams are closed, Failure may occur.
scala> import org.bitbucket.franck44.expect.Expect import org.bitbucket.franck44.expect.Expect scala> val e = Expect("src/test/resources/test-terminal.sh", List("ready>")) e: org.bitbucket.franck44.expect.Expect = Expect(src/test/resources/test-terminal.sh, List("ready>")) // External process will send "read" after 4 seconds and time bound is 3 seconds scala> e.send("read::4\n") ; e.expect("""ready>""".r, 3.seconds) res0: scala.util.Try[String] = Failure(java.util.concurrent.TimeoutException: Futures timed out after [3 seconds]) // kill the external process scala> e.destroy() // Output stream is closed and results in an exception scala> e.send("read::4\n") ; e.expect("""ready>""".r, 5.seconds) res2: scala.util.Try[String] = Failure(java.util.NoSuchElementException)
If the process generates the answer in successive chunks (with a delay in between the chunks) the
expect method collects the characters preceeding the prompt:
scala> import org.bitbucket.franck44.expect.Expect import org.bitbucket.franck44.expect.Expect // output "read" after 4 seconds and "write" after 6 seconds scala> e.send("read::4 write::2\n") ; e.expect("""ready>""".r, 7.seconds) res1: scala.util.Try[String] = Success( read write )
In the previous example, ''read'' is generated after 4 seconds and ''write'' 2 seconds after ''read''. The prompt follows immediatly and the full answer "\n\nread\nwrite" is collected (the prompt is not part of the string returned by
uses java.util.concurrent.ExecutorService: the process runs in an isolated thread and when an exception occurs (timeout, not schedulable) this thead is shutdown (and all the tasks submitted to it cancelled). Thanks to Dominik Klumpp for suggesting this improvement.
improved handling of exceptions (message).
First-cut workable version of the package.
- Uses scala.Futures, Await to expect the result.