Please move down to the 2nd update. I didn't want to change the previous context of this question.
I'm using wkhtmltoimage from a Java app.
The standard way of using it is - path-to-exe http://url.com/ image.png.
According to their docs, if we write a - instead of an input URL, the input shifts to STDIN.
I'm starting the process using ProcessBuilder -
ProcessBuilder pb = new ProcessBuilder(exe_path, " - ", image_save_path);
Process process = pb.start();
Now I'm unable to figure out how to pipe an input stream to this process.
I have a template file read into a DataInputStream, and I'm appending a string at the end:
DataInputStream dis = new DataInputStream (new FileInputStream (currentDirectory+"\\bin\\template.txt"));
byte[] datainBytes = new byte[dis.available()];
dis.readFully(datainBytes);
dis.close();
String content = new String(datainBytes, 0, datainBytes.length);
content+=" <body><div id='chartContainer'><small>Loading chart...</small></div></body></html>";
How do I pipe content to the STDIN of the process?
UPDATE---
Following the answer by Andrzej Doyle:
I've used the getOutputStream() of the process:
ProcessBuilder pb = new ProcessBuilder(full_path, " - ", image_save_path);
pb.redirectErrorStream(true);
Process process = pb.start();
System.out.println("reading");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
bw.write(content);
Doing so gives an error saying:
Exception in thread "main" java.io.IOException: The pipe has been ended
2nd UPDATE--------
The current code block is as such:
try {
ProcessBuilder pb = new ProcessBuilder(full_path, "--crop-w", width, "--crop-h", height, " - ", image_save_path);
System.out.print(full_path+ "--crop-w"+ width+ "--crop-h"+ height+" "+ currentDirectory+"temp.html "+ image_save_path + " ");
pb.redirectErrorStream(true);
Process process = pb.start();
process.waitFor();
OutputStream stdin = process.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
// content is the string that I want to write to the process.
writer.write(content);
writer.newLine();
writer.flush();
writer.close();
} catch (Exception e) {
System.out.println("Exception: " + e);
e.printStackTrace();
}
Running the above code gives me an IOException: The pipe is being closed.
What else do I need to do to keep the pipe open?
Exception in thread "main" java.io.IOException: The pipe has been ended
This means the process you have started has died. I suggest you read the output to see why. e.g. did it give you an error.
Is there a reason you are using DataInputStream to read a simple text file? From the Java documentation
A data input stream lets an application read primitive Java data types
from an underlying input stream in a machine-independent way
It's possible that the way you are reading the file causes an EOF to be sent to the outputstream causing the pipe to end before it gets to your string.
You requirements seems to be to read the file simply to append to it before passing it on to the wkhtmltoimage process.
You're also missing a statement to close the outputstream to the process. This will cause the process to wait (hang) until it gets an EOF from the input stream, which would be never.
I'd recommend using a BufferedReader instead, and writing it directly to the outputstream before appending your additional string. Then call close() to close the stream.
ProcessBuilder pb = new ProcessBuilder(full_path, " - ", image_save_path);
pb.redirectErrorStream(true);
Process process = null;
try {
process = pb.start();
} catch (IOException e) {
System.out.println("Couldn't start the process.");
e.printStackTrace();
}
System.out.println("reading");
try {
if (process != null) {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader inputFile = new BufferedReader(new InputStreamReader(new FileInputStream(currentDirectory+"\\bin\\template.txt")));
String currInputLine = null;
while((currInputLine = inputFile.readLine()) != null) {
bw.write(currInputLine);
bw.newLine();
}
bw.write("<body><div id='chartContainer'><small>Loading chart...</small></div></body></html>");
bw.newLine();
bw.close();
}
} catch (IOException e) {
System.out.println("Either couldn't read from the template file or couldn't write to the OutputStream.");
e.printStackTrace();
}
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String currLine = null;
try {
while((currLine = br.readLine()) != null) {
System.out.println(currLine);
}
} catch (IOException e) {
System.out.println("Couldn't read the output.");
e.printStackTrace();
}
Remove the whitespace from " - " -- the normal whitespaces are removed by the shell parser, but here in the ProcessBuilder, it's interpreted as the (literal) filename beginning and ending with a whitespace.
(Actually looking at the process's output as Peter suggested would probably have told you so...)
After you create the Process object, you can call getOutputStream() in order to get hold of a stream that sends its contents to the process' standard input.
Once you have hold of this you can use Java's standard IO to write whatever bytes to the this stream you want (including wrapping it in a Writer, adding buffering, etc.) - and as you write them, they'll be read by the process as soon as they're flushed.
The following code works as well:
import java.io.*;
import java.util.*;
public class ProcessTest {
public static void main(String[] args) throws Exception {
ProcessBuilder pb = new ProcessBuilder("/home/me/stdinecho");
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process proc = pb.start();
// Input file
DataInputStream din = new DataInputStream((new FileInputStream("/home/me/stdinecho.cp")));
byte[] dinBytes = new byte[din.available()];
din.readFully(dinBytes);
din.close();
String content = new String(dinBytes, 0, dinBytes.length);
content = "header\n" + content + "\nfooter";
BufferedInputStream procStdout = new BufferedInputStream(proc.getInputStream());
OutputStream stdin = proc.getOutputStream();
stdin.write(content.getBytes());
stdin.flush();
}
}
Here stdinecho.cpp is the program that outputs the line entered on its prompt:
#include <iostream>
#include <fstream>
#include <cstdio>
using namespace std;
int main()
{
string strOutput;
string str;
while(getline(cin, str)) {
cout << str << endl;
}
}
Related
I am writing a Java code that will create and write a shell command into temporary file which will be then used to run using process builder.
File file = null;
InputStream input = getClass().getResourceAsStream("/somecommand.sh");
try {
file = File.createTempFile("tempcmdline", ".sh");
} catch (IOException e1) {
e1.printStackTrace();
}
OutputStream out;
try {
out = new FileOutputStream(file);
BufferedReader reader=new BufferedReader(new InputStreamReader(input));
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(out));
String line;
while((line = reader.readLine()) != null) {
writer.write(line);
}
} catch (IOException e1) {
e1.printStackTrace();
}
Process p;
try {
List<String> cmdList = new ArrayList<String>();
cmdList.add("/usr/bin/bash");
cmdList.add("tempcmdline.sh");
ProcessBuilder pb = new ProcessBuilder(cmdList);
pb.redirectErrorStream(true);
p = pb.start();
IOUtils.copy(p.getInputStream(), System.out);
p.waitFor();
BufferedReader reader=new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
if((line = reader.readLine()) != null) {
System.out.println("some display" + line);
} else {
System.out.println("some other display");
}
I am getting error that tempcmdline.sh is not found. I tried adding /tmp/tempcmdline thinking default temporary directory used by createTempFile will be /tmp on UNIX.
Please share any working code where we can specify a directory and use it in process.
[EDIT] I tried getting absolutepath using file.getAbsolutePath() and pass full path name in process. However this is giving empty response (when I read the process output with InputStreamReader) while when I run the shell script manually on Unix it gives me a proper 1 line o/p message.
[EDIT] I figured out that temporary file getting created has \r which is causing the issue.
[UPDATE] Following is the updated code that worked for me:
File file = null;
InputStream input = getClass().getResourceAsStream("/someCommand.sh");
try {
file = File.createTempFile("tempcmdline", ".sh");
String tempShell = file.getAbsolutePath();
Files.copy(input, Paths.get(tempShell), REPLACE_EXISTING);
file.deleteOnExit(); //comment for testing to see how it is written
} catch (IOException e1) {
e1.printStackTrace();
}
Process p;
try {
String tempShellFile = file.getAbsolutePath();
List<String> cmdList = new ArrayList<String>();
cmdList.add("sh");
cmdList.add(tempShellFile);
cmdList.add(applicationName);
cmdList.add(serviceAccount);
ProcessBuilder pb = new ProcessBuilder(cmdList);
//pb.redirectErrorStream(true);
p = pb.start();
//IOUtils.copy(p.getInputStream(), System.out); //uncomment for testing
p.waitFor();
BufferedReader reader=new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
if((line = reader.readLine()) != null) {
System.out.println("some message");
} else {
System.out.println("some other message");
}
}catch (IOException e) {
e.printStackTrace();
}catch (InterruptedException e) {
e.printStackTrace();
}
Error details and learnings to share:
1) Initial issue was that response from process was leading to no InputStream. To debug I used IOUtils.copy(p.getInputStream(), System.out);
Then, actual error showed up from process stating no such file found (tempcmdline.sh)
2) Upon understanding that a temporary file name will be different, I got the absolute path and passed it to the process.
Next error was no response as though shell script was empty. Loop above in initial code did not handle newline and was incorrect. New code added has the fix.
Next error was invalid characters "\r" which was due to shell script file created on Windows. I simply cleaned it on Eclipse editor and it was fine.
3) After debugging I removed IOUtils.copy step as I wanted the output to be read through Inputstream.
Hope it helps someone..
It appears that you are attempting to write to a temp file, named something like "/tmp/tempcmdline234765876.sh", and then you are attempting to run "tempcmdline.sh".
Add a System.out.println(file); to see what the actual temp filename is.
I try to get my penplotter to work from within java.
I have a start but I don't know how to continue.
This is what I have:
public static void main(String[] args) {
try {
Runtime runTime = Runtime.getRuntime();
Process process = runTime.exec("chiplotle");
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
System.out.println("this prints fine");
while ((line = in.readLine()) != null) {
System.out.println(line);
}
System.out.println("it never reaches this...");
}
catch (IOException e) {
e.printStackTrace();
}
}
This is the output in the console:
I typed the 11 myself. But it doesn't do anything with it.
Also it never prints:
System.out.println("it never reaches this...");
So it looks like my program is halted for input, is that correct?
And how can I get further?
You should read from the InputStream in a bacgkround thread.
You need to get the Process's OutputStream and then write to it.
OutputStream os = process.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
PrintWriter pw = new PrintWriter(bw);
// now you can write to the Process, i.e., pw.println("11");
You will need to not just print but also to analyze the text that your InputStream sends you to decide when to write back to the process via the PrintWriter.
I have searched through similar questions here on Stack Overflow, however, I can't seem to make this work.
I have a java program that must use ProcessBuilder to load a C executable file. The file just accepts a string through the CLI and converts it to upper case. The java program creates a system process with ProcessBuilder to manage this executable, and must send the string and receive the converted one to print it in the CLI.
This is the code for uppercases.c:
#include <stdio.h>
int main(int argc, char *argv[]) {
char text[1024];
scanf("%s", &text[0]);
int i;
for(i = 0; i < strlen(text); i++) {
text[i] = toupper(text[i]);
}
printf("%s\n", text);
return 0;
}
I compiled it using
$ gcc uppercases.c -o uppercases
And ran it with
$ ./uppercases
Everything works fine. Now this is the code for Uppercase.java. I have to create an OutputStream to send the string to the C executable (uppercases), and then I create an InputStream to save its output and print it to the CLI:
public class Uppercase {
public static void main(String[] command) {
String textIn, textOut;
Scanner reader = new Scanner(System.in);
// This is what we want to send
System.out.println("Write something: ");
textIn = reader.nextLine();
try {
// Here I create a process to handle "uppercases"
Process process = new ProcessBuilder(command).start();
OutputStream os = process.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
// According to me, this will send the string to "uppercases"
bw.write(textIn);
bw.flush();
// This is to read the output of "uppercases"
InputStream is = process.getInputStream();
BufferedReader br = new BufferedReader (new InputStreamReader(is));
while ((textOut = br.readLine()) != null) {
System.out.println(textOut);
}
os.close();
is.close();
} catch (IOException e) {
System.out.println("I/O error:" + e.getMessage());
} catch (Exception e) {
System.out.println("Error:" + e.getMessage());
}
}
}
To compile, I type:
$ javac Uppercase.java
And to execute:
$ java Uppercase ./uppercases
The problem is that when I type a string and hit enter, the cursor stays forever there, I hit enter again and nothing happens, and finally I have to press CTRL+C to exit. Any help will be appreciated.
All works fine with your java program with one exception : you use a BufferedWriter that you correctly flush, but as the line that you write does not contain a newline, the C program still waits for more input.
If you write :
// According to me, this will send the string to "uppercases"
bw.write(textIn);
bw.write("\n");
bw.flush();
It is enough and the program terminates normally.
But if you really want the things to be bullet proof, you should close bw to clearly indicate to the subprocess that is should not wait for more input :
// According to me, this will send the string to "uppercases"
bw.write(textIn);
bw.close();
Then the program terminates normally (and correctly) even without the end new line. Of course in that case the last os.close() is no longer necessary but is still harmless.
I'm working on reading the output of a script that is invoked using a Java process. However, in the middle of the script run, it will in SOME situations prompt the user to answer y/n to continue. However, after reading many posts on StackOverflow, I'm still stuck with detecting the prompt and then sending the response while the process is still running.
If anyone has any ideas, that would be awesome.
I've tried reading from Scanner class and System.console to no prevail.
Here is a portion of the code I'm using.
Process p;
String file = "./upgrade.sh";
cmds.add(file);
cmds.add(sourcePath);
cmds.add(outputDirectoryPath);
cmds.add(zip);
cmds.add("-c");
//}
pb = new ProcessBuilder(cmds);
pb.directory(new File(binDir));
p = pb.start();
BufferedReader reader = new BufferedReader (new InputStreamReader(p.getInputStream()));
BufferedReader reader2 = new BufferedReader (new InputStreamReader(p.getErrorStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
try
{
String line = reader.readLine();
while (line != null)
{
System.out.println(line);
line = reader.readLine();
}
reader.close();
writer.close();
}
catch (IOException e)
{
e.printStackTrace();
}
p.destroy();
The problem is that you use a BufferedReader. It will only return when it has read a full line, that is a line separator. But if the script asks for something with a prompt, there won't be a line separator! As a result it WILL NOT return.
You have to use some other kind of reader in order to control the process.
I have the following code in java that calls the date command in the command prompt:
// prepare command prompt runtime and process
Runtime runtime = null;
Process process = null;
// prepare output stream
OutputStream outputStream = null;
try {
runtime = Runtime.getRuntime(); // instantiate runtime object
process = runtime.exec("date"); // get the current date in command prompt
// read the output of executing date command
outputStream = process.getOutputStream();
// output the date response
System.out.println(outputStream);
process.waitFor(); // wait for the date command to finish
} catch(Exception e) {
e.printStackTrace();
} // end catch
How can I read the outputStream value for me to be able to use the System.output.println()
You don't read the output stream, you write to it to pass data to process. To read the data from process use
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
br.readLine();
The code is for string output of process. Of course if your process outputs data in other way you have to change the wrappers around process.getInputStream()
Update: I think it is in some way confusing that we use getInputStream to actually read process output :) The reason is that initially basic classes OutputStream and InputStream were named so relatively to the code that uses them (the code you write). So when you use OutputStream you actually use it as output for your program. When you use process.getOutputStream you don't get process' output but instead get your program output which is piped to process input. When you use process.getInputStream you get input for your program which obtains data piped from process' output.
you can do like this way without using OutputStream object
Process p = Runtime.getRuntime().exec("date");
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
String answer = sb.toString();
System.out.println(answer);