jthurman
jthurman

Reputation: 465

Capturing output and input of interactive program with bash

I'm working on automating an interactive command line Java program in bash to validate that the program produces proper output for the input (basically poor-man's unit testing in bash).

For example, if I have a java program that asks the user to enter their full name, and then outputs only their first name, it should look something like this:

Enter your name: John Doe
John

Where "John Doe" was typed in by the user.

A simple bash command to run this might look like this:

OUTPUT=`echo "John Doe" | java NameReader`

or

OUTPUT=`java NameReader <<< "John Doe"`

The trouble with both of these is that $OUTPUT now contains the following:

Enter your name: John

Because the text that was sent to stdin (and its newline accompanying) isn't reproduced in the output of the program the way we would see it in the console.

Ideally, $OUTPUT would contain this:

Enter your name: John Doe
John

But I could live with this:

Enter your name: 
John

(The input is omitted entirely, but the output is on a new line, as expected)

Is there a way in bash (without altering the underlying java program) to get the text that is being piped to stdin to also pipe to stdout at the "time" it's read by the java program, so the full interactive session is captured?

(One more note: Some searching indicated that the spawn/expect commands might be helpful, but the system this will run on does not appear to have them available)

Upvotes: 5

Views: 4200

Answers (3)

jthurman
jthurman

Reputation: 465

Here's what I ended up doing. It's not a complete solution, but it works in this case and may be useful to someone else facing something similar later:

{ sleep 3; echo "John Doe" | tee -a out.txt; } | java  NameReader >> out.txt
OUTPUT=`cat out.txt`
rm out.txt

This causes the script to do two things in parallel:

  1. Wait 3 seconds, then write the text "John Doe" to both the pipe and the file out.txt
  2. Run the java program.

By using tee, the text gets written to both the pipe (which the java program is waiting to read), and to the file. The -a option appends the text to the file instead of overwriting it.

The java program also appends its content to out.txt (using >>).

Then we read the file contents into the OUTPUT variable and delete the file.

This isn't really a complete solution, because...

  1. It works as long as the java program is running and waiting for the user input within the 3 second sleep. If it's not, then the output will be garbled. While it's reasonable (in this case) to expect the program to be waiting for input after 3 seconds, it's certainly not guaranteed.
  2. The 3 second delay makes the process slower than necessary. If the java program was ready for input after only 1 second, then we're spending 2 extra seconds doing nothing. If this were to be run many times, that could add up.
  3. It's only really applicable to interactive programs that take a single piece of input. It might be possible to use multiple "sleep/echo/tee" constructs with different sleep lengths to respond to multiple lines, but that seems like it would get both complex and slow very quickly.

...So this is the best I've got for now. Better solutions are still invited, of course.

Upvotes: 0

Adam
Adam

Reputation: 18807

You can use the script command

script -q -c "java NameReader" log.txt

This will record the input and ouput of the java NameReader command in the log.txt file.

Upvotes: 6

Simonlbc
Simonlbc

Reputation: 651

If your java program is like this:

import java.util.Scanner;
class A {
        public static void main(String[] args) {
                Scanner sc = new Scanner(System.in);
                System.out.println(sc.next());
        }
}

then you could build a bash script that would parse an input of the form

Enter your name: John
John Doe

to transform it into

Enter your name: John Doe
John

Here is one possible bash script:

#!/bin/bash
arg1Outpt=`eval "$1"`
javaOut=`echo "$arg1Outpt" | eval "$2"`
#prints Enter your name: John\nJohn Doe      
echo "${javaOut}"$'\n'"${arg1Outpt}" |
                        sed 'N; s/\(Enter your name: \)\(.*\)\(\n\)\(.*\)/\1\4\3\2/'
                        #and this is a multiline sed(man sed) that transforms your 
                        #input into what you want :)

use it like this:

bash pipeCheat.bash 'echo "john doe"' 'java A'

where pipeCheat.bash is the name of the file where you saved the script above.

If you have questions don't hesitate to ask.

Upvotes: 1

Related Questions