Opening a Shell and Interacting with Its I/O in Java

opening a shell and interacting with its I/O in java

Your problem is that the standard input and output of the xterm process don't correspond to the actual shell that is visible in the terminal window. Rather than an xterm you may have more success running a shell process directly:

Process pr = new ProcessBuilder("sh").start();

Executing interactive shell commands in Java

I think that you even explained the answer into your question.

When you are running external application from java either using Runtime.exec() or ProcessBuilder you can access standard output and standard input streams. User input stream to read what external application writes and output stream to send commands to external application.

But be careful. Some applications will not get your commands sent from java. For example command ssh in Unix system is designed to avoid its usage by non-human user (e.g. other application). It require to be executed from terminal for security reasons, so you cannot for example run ssh otherhost and then send user/password from java. If you need this you have to run the command via other command line utility named expect that simulates terminal and is driven by script.

Running an interactive shellscript using java

Read the process output stream and check for the input prompt, if you know the input prompt, then put the values to process inupt stream.
Otherwise you have no way to check.

ProcessBuilder pb = new ProcessBuilder(command);
pb.redirectErrorStream(true);
BufferedReader b1 = new BufferedReader(new InputStreamReader(p.getInputStream()));
BufferedWriter w1 = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
String line = "";
String outp = "";
while ((line = b1.readLine()) != null) {
if (line.equals("PLEASE INPUT THE VALUE:")) {
// output to stream
w1.write("42");
}
outp += line + "\n";
}

...

UPD: for your code it should be something like that

    ProcessBuilder pb2=new ProcessBuilder("/home/ahijeet/sample1.sh","--ip="+formobj.getUpFile().getFileName(),"--seqs="+seqs);

script_exec = pb2.start();

OutputStream in = script_exec.getOutputStream();

InputStreamReader rd=new InputStreamReader(script_exec.getInputStream());

pb2.redirectError();

BufferedReader reader1 =new BufferedReader(new InputStreamReader(script_exec.getInputStream()));

StringBuffer out=new StringBuffer();

String output_line = "";

while ((output_line = reader1.readLine())!= null)
{
out=out.append(output_line+"/n");
System.out.println("val of output_line"+output_line);

//---> i need code here to check that whether script is prompting for taking input ,so i can pass it values using output stream
if (output_line.equals("PLEASE INPUT THE VALUE:")) {
// output to stream
in.write("42");
}

}

Command line terminal executing on process and input interaction from this process

To implement such use case I would personally use:

  1. An Observable object to notify my UI when a new line has been provided by the external process
  2. An Observable object to which I add new lines provided by my UI
  3. An Observer of #1 that will refresh the data of my UI
  4. An Observer of #2 that will send the lines provided by my UI to my external process
  5. A Thread that will check if a new line has been provided by my external process and if so it will provide those lines to #1

So as I don't have your full env, I will show you how it will work with mock objects:

First my fake external application that only does an Echo of what he receives:

public class Echo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
String line = scanner.nextLine();
System.out.printf("echo > %s%n", line);
}
}
}

If this class receives foo, it will print into the standard output stream echo > foo

Then my Observable class

public class ObservableStream extends Observable {
private final Queue<String> lines = new ConcurrentLinkedQueue<>();

public void addLine(String line) {
lines.add(line);
setChanged();
notifyObservers();
}

public String nextLine() {
return lines.poll();
}
}

NB: The class ObservableStream (as it is implemented so far) is meant to have only one Observer no more which is enough according to your needs. Indeed is only used to decouple your UI from how the data is retrieved or published

Then finally the main code:

Process p = Runtime.getRuntime().exec(
new String[]{"java", "-cp", "/my/path/to/my/classes", "Echo"}
);
// The Observable object allowing to get the input lines from my external process
ObservableStream input = new ObservableStream();
// A mock observer that simply prints the lines provided by the external process
// but in your case you will update your text area instead
input.addObserver(
(o, arg) -> {
ObservableStream stream = (ObservableStream) o;
String line;
while ((line = stream.nextLine()) != null) {
System.out.printf("Line Received from the external process: %s%n", line);
}
}
);
// The thread that reads the standard output stream of the external process
// and put the lines into my variable input
new Thread(
() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream()))
) {
String line;
while ((line = reader.readLine()) != null) {
input.addLine(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
).start();
// The Observable object allowing to send the input lines to my external process
ObservableStream output = new ObservableStream();
// Observer that simply sends to my external process line by line what we put in
// the variable output
PrintWriter writer = new PrintWriter(p.getOutputStream(), true);
output.addObserver(
(o, arg) -> {
ObservableStream stream = (ObservableStream) o;
String line;
while ((line = stream.nextLine()) != null) {
writer.println(line);
}
}
);

// A simple scanner used to send new messages to my external process
Scanner scanner = new Scanner(System.in);
while (true) {
output.addLine(scanner.nextLine());
}

If this code receives foo, it will print into the standard output stream Line Received from the external process: echo > foo

(Blocking) interactive shell via ProcessBuilder

I decided to rewrite my binary to be non-interactive again. It turns out the expected performance gain was negligible so there was no more reason to keep it interactive and go through an increased implementation hassle.



Related Topics



Leave a reply



Submit