I use a ProcessBuilder to run system command from java. The system command may ask input data from user. Program failed when the system command asks input data from user for multiple times. Example of running such a command from command-line directly:
>test-input
Continue? Y/N
y
Entered y
Again: Continue? Y/N
y
Entered y
If I use my ProcessBuilder based program to run "test-input", it either hangs or failed to take input for a second time. Here is the code of reading/writing logic. Read from input stream (Exception handling and stream close logic is omitted)
ProcessBuilder pb = new ProcessBuilder(cmdList);
pb.redirectErrorStream(true);
pb.directory(new File("some-test-dir"));
process = pb.start();
InputStream is = process.getInputStream();
int value = -1;
while ( (value = is.read()) != -1) {
reader.append((char)value);
}
int result = process.waitFor();
Write to output stream:
public void write(String s) {
OutputStream os = null;
try {
os = process.getOutputStream();
os.write(s.getBytes(Charset.forName("UTF-8")));
}
catch (IOException e) {
//...
}
finally {
// Problematic
os.close();
}
}
The problem occurred at the line os.close(). If I put it there, the output stream is closed after the first input data is processed, thus it cannot be re-opened and program cannot take the second input data. If I do not close the output stream, then program hangs there as is.read() gets blocked forever. How to solve this issue? thanks
Problem is fixed by writing a new line character for each input, as described in: Writing to InputStream of a Java Process
os.write(s.getBytes(Charset.forName("UTF-8")));
os.write('\n');
os.flush();
Related
I want to spawn a child process in Java and send different commands inside the app. My child process has authentication and each user can have a variety of internal commands.
For example:
> login myuser passowrd
OK
> list certs
cert1 abc
cert2 efg
> logout
> exit
Well, to simulate that I will make my example with "node" as IO CLI.
public class JAVAMain {
public static void main(String[] args) throws Exception {
action();
}
public static String action() throws Exception {
ProcessBuilder pb = new ProcessBuilder("node");
pb.redirectErrorStream(true);
Process process = pb.start();
// streams
InputStream stdout = process.getInputStream();
OutputStream stdin = process.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
String buff = "";
String res = "";
System.out.println("1");
writer.write("console.log('OK');\n"); // simulate "login answer"
writer.flush();
System.out.println("2");
res = "";
while ((buff = reader.readLine()) != null) {
res += buff;
}
if (!res.equals("OK")) {
reader.close();
writer.close();
throw new Exception("Invalid auth");
}
System.out.println("3");
writer.write("console.log('any text...');\n");
writer.flush();
System.out.println("4");
res = "";
while ((buff = reader.readLine()) != null) {
res += buff;
}
reader.close();
writer.close();
return res;
}
}
I expect to print 1,2,3,4 and get the res any text... for this example. But the program never stops and stay in 1,2. If I close the writer after the flush in 1 I get this output:
1
2
3
Exception in thread "main" java.io.IOException: Stream closed
at java.io.BufferedWriter.ensureOpen(BufferedWriter.java:116)
at java.io.BufferedWriter.write(BufferedWriter.java:221)
at java.io.Writer.write(Writer.java:157)
at com.keynua.kades.JAVAMain2.action(JAVAMain2.java:48)
at com.keynua.kades.JAVAMain2.main(JAVAMain2.java:14)
That's why I close the writer and the reader works but I can't write again. So, how I can make to send multiples commands to the child app and read the output to follow the flow with other commands?
One problem is that your while loops
while ((buff = reader.readLine()) != null) { ... }
only terminate when the reader has reached the end of the input stream.
The end of the input stream is only reached when the subprocess terminates.
The second problem is that you seem to be using NodeJS as sample command executor.
If NodeJS is started from a console, you can enter JavaScript statements and they are executed one by one.
You are however starting NodeJS not from a console, but from some other application. In this case, NodeJS wants to read a complete script from stdin and executes the complete script at once
You could start NodeJS with the -i parameter (force interactive mode), at the expense of some additional output.
To achieve this, you would create the ProcessBuilder with
ProcessBuilder pb = new ProcessBuilder("node", "-i");
Communicating with a subprocess in this way only works when you know how many lines to read from the reader before sending the next command.
Knowing how many lines to read can mean:
knowing how many lines of output a command produces (login: 1 line of output, logout: no output)
knowing that a command produces a distinct last line (for example an empty line or a line with only "END" in it)
that a command produces a line count as the first result
executing another command first that returns the result line count of the subsequent command
The list certs command could either:
produce
cert1 abc
cert2 def
END
produce (where the last line would be empty instead of containing a dot)
cert1 abc
cert2 def
.
produce
2 certs
cert1 abc
cert2 def
or you could execute a count certs command before executing list certs
I am calling a bash scrip script from Java.
The script does the following:
cat /home/user/Downloads/bigtextfile.txt | grep 'hello'
This particular command when run command line takes about 1 second to complete on the text file which is 150MB.
When calling the bash script via Java using the following call:
command = "sh /home/user/bashfiletocall"
p = Runtime.getRuntime().exec(command);
The time to complete takes so long I don't wait.
Am I doing something very wrong and if not can you explain the reason for the huge lack in performance?
NOTE: I was running it in Netbeans and this seems to be the problem .. when I ran the file command line it was quick. The performance between execution in netbeans and command line is huge.
Many thanks.
private String executeCommand(String command) {
StringBuilder output = new StringBuilder();
BufferedReader reader = null;
Process p;
try {
p = Runtime.getRuntime().exec(command);
p.waitFor();
reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
while ((line = reader.readLine())!= null) {
output.append(line + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
After starting your process you need start reading from the input stream. Otherwise the buffers are running full and p.waitFor() waits forever.
Javadoc of the Process class:
Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.
I am trying to run plink in my own console window. I started by using Process.exec() and that worked fine. The I moved to using ProcessBuilder and now the output is not sent out until I kill the process.
My code looks like this:
class ConsoleOutputThread extends Thread {
public void start(String processName) {
// this was old code: Runtime r = Runtime.getRuntime();
try {
builder = new ProcessBuilder("plink", "-ssh", "192.168.3.21");
builder.redirectErrorStream(true);
process = builder.start();
//this was old code: process = r.exec (processName);
} catch (IOException ex) {
}
start();
}
#Override
public void run() {
try {
BufferedReader is = new BufferedReader(new InputStreamReader(process.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
try {
process.waitFor();
} catch (InterruptedException ex) {
}
char b[];
b = new char[1];
while(is.read(b, 0, 1)> 0) {
// this is for debug, normally sent to console
System.out.println("Got character: " + b[0]);
}
} catch (IOException ex) {
}
}
}
So, when using Runtime.exec() everything worked fine. Now, with ProcessBuilder, the read function blocks forever (actually until I kill the process, when everuthing is spitted out). However, the error stream works, i.e. if I put a bad option I get the messages in the console.
I am probably missing something here and looking for help.
Thank you
You've set the plink process to write its output to a pipe which is connected to the java process. Anything output by the plink process will be saved in an operating-system buffer until your process reads it. The OS buffer has a limited capacity,, and if plink writes too much data, then it will block until your process reads some data from the buffer.
Unfortunately, the java process waits for the plink process to complete before reading anything from the pipe. So, if the plink process writes too much output, it will block indefinitely.
You should change the java logic to read the plink process's output before calling waitfor().
I'm trying to find a solution how to implement a multiple command - response interaction with the Windows cmd shell. Example:
Start the cmd shell
"dir"
wait for and Handle input
Execute new command depending on the input content
wait for and Handle input
etc.
PLEASE NOTE! Steps above were only to describe the way of communication, it is NOT my intention to browse the file system, i.e. the actual commands could be something else.
Approach so far:
try {
Runtime rt = Runtime.getRuntime();
p = rt.exec("cmd");
error = p.getErrorStream();
input = p.getInputStream();
output = new PrintStream(p.getOutputStream());
StreamGobbler errGobbler = new StreamGobbler(error, "ERROR");
StreamGobbler inGobbler = new StreamGobbler(input, "INPUT");
errGobbler.start();
inGobbler.start();
output.println("dir");
output.flush();
sleep(5);
output.println("dir");
output.flush();
} catch (IOException e) {
System.out.println(e.printStackTrace());
}
StreamGobbler class:
class StreamGobbler extends Thread
{
InputStream is;
String type;
ArrayList<String> cmdRespArr = new ArrayList<String>();
StreamGobbler(InputStream is, String type) {
this.is = is;
this.type = type;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader bf = new BufferedReader(isr);
String line = null;
while ( ( line = bf.readLine() ) != null ) {
cmdRespArr.add(line);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
In this example however the while loop in the run method never returns between the issued commands (this is not part of the problem).
So, would the approach be to let the while method add the line read to a collection or other container, and then monitor that one for something indicating that the cmd shell is ready for input (which would in turn indicate that all available input from previous command have been read). And then fire off the next command?
In the example above this indication would get rid off the sleep call which right now is only there for debugging purposes.
I have a vague memory that this was the approach when doing it with Python.
Or is this totally wrong?
Will it be a solution to start multiple command processors, i.e. one per command?
I'm asking because with keeping one command processor open, it is very hard to determine when a command has been processed, unless you parse the output line by line and wait until you see the prompt in the output.
With multiple processors, i.e. executing "cmd /c dir" then input output redirs will close when the command has completed (and the associated process terminated).
Of course this will not work, if some commands depend on others, e.g. doing a chdir and expecting the next command to work in that dir.
I dont think this is possible, but I have been using:
Process p = Runtime.getRuntime().exec(command);
to run commands on the command line, but now I have come accross a situation where the command I am running part way through will ask for some user input, for example a username.
This can not be resolved by a argument to the command that is being exec, is there any way I can pass the username to the same command line instance and continue?
---EDIT---
I still cant get this to work. These are the steps on the command line:
C:\someProgram.exe
Login:
Passowrd:
So I need to pass the login and password when it prompts at runtime. The code I've got that doesnt work:
try {
String CMD = "\"C:\\someProgram\"";
Scanner scan = new Scanner(System.in);
ProcessBuilder builder = new ProcessBuilder(CMD);
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream is = process.getInputStream();
BufferedReader reader = new BufferedReader (new InputStreamReader(is));
OutputStream out = process.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
String line;
try {
while (scan.hasNext()) {
String input = scan.nextLine();
if (input.toLowerCase().startsWith("login")) {
writer.write("myUsername");
} else if(input.toLowerCase().startsWith("password")){
writer.write("myPassword");
}
writer.flush();
line = reader.readLine();
while (line != null) {
System.out.println ("Stdout: " + line);
line = reader.readLine();
}
if (line == null) {
break;
}
}
process.waitFor();
}
finally {;
writer.close();
reader.close();
}
}
catch (Exception err) {
System.err.println("some message");
}
Ive tried things like:
writer.write("myUsername\n");
Any help, i can see that someProgram.exe is called and running in the processes, but it just hangs.
Just write to p.getOutputStream(). That'll send the username to the process's standard input, which should do what you want.
out = p.getOutputStream();
out.write("fooUsername\n".getBytes());
out.flush();
You should redirect the command input and send your parameters there. Use process.setInputStream(), the write into this stream.
As part your command String, if you are running on Unix/Linux/OSX and maybe PowerShell, you could prepend the cat shell command to have the shell dump the contents of a file into the input stream for your intended executable to read as input.
A command something like cat user_input.txt | myAppWantsInput.pl.
This will take the content of user_input.txt, dump it into standard-in, so when "myAppWantsInput.pl" in your command executes, and reads from standard-in, it will be reading the contents of the file and taking that as input as if entered from the keyboard.
Of course, it you don't know a priori what values you intend to pass, you could generate the files you need dynamically before invoke the command. This won't work if you can't determine all the input you'll want before you run the command.