如何将InputStream连接到ProcessBuilder

8

请往下滑到第2个更新。我不想改变这个问题的先前上下文。

我正在从Java应用程序中使用wkhtmltoimage。

标准使用方法是 - path-to-exe http://url.com/ image.png

根据他们的文档,如果我们用-代替输入URL,则输入会转换为STDIN。

我正在使用ProcessBuilder启动进程 -

ProcessBuilder pb = new ProcessBuilder(exe_path, " - ", image_save_path);

Process process = pb.start();

现在我无法找出如何将输入流导入到这个进程中。
我已经读取了一个模板文件到一个DataInputStream对象中,并在末尾添加了一个字符串。
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>";

我该如何将内容导入到进程的STDIN中?

更新---

根据Andrzej Doyle的答案:

我使用了进程的getOutputStream()方法:

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);

这样做会出现错误,错误信息如下:

Exception in thread "main" java.io.IOException: The pipe has been ended

第二次更新--------

目前的代码块如下:

    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();
    }

运行以上代码会出现一个IOException: The pipe is being closed.的错误。
还需要做什么来保持管道的开放状态?

你不应该把pb.start放在程序结尾吗?(在bw.write(content);之后。这只是一种基于“如何在进程启动后写入进程”的直觉。) - Sap
@Grrrrr 嗯,我认为我们需要启动一个进程才能获取它的输出流。 - Hrishikesh Choudhari
3
您正在向输出流中写入数据,这意味着“您正在向进程中写入数据!”如果您想获得进程的输出,则应执行process.getInputStream,然后读取它。 - Sap
1
你是否在读取错误流?也许子进程正在发出错误并终止。如果你没有读取错误流,你永远不会知道。 - Christopher Schultz
1
我认为getOutputStream这个名字对于实际用途来说非常不明确:返回连接到子进程正常输入的输出流。向该流输出的内容将被管道传输到由此Process对象表示的进程的标准输入中。 在Python中,它只是被称为stdin,但是有人可能会认为写入名为stdin的东西在语义上不正确。 - Mmmh mmh
5个回答

7

主线程中的异常:java.io.IOException: 管道已结束

这意味着您启动的进程已经停止。建议您阅读输出以查看原因,例如是否出现错误。


该进程的错误流也说了同样的话 - “管道已结束”。 - Hrishikesh Choudhari
我从未见过一个程序在从命令行运行时会出现这样的错误。 - Peter Lawrey
我从错误中找到了这个输出:java.io.BufferedReader@2512b853 - Hrishikesh Choudhari
这意味着您正在打印 BufferedReader 对象,而不是使用 readLine() 读取它并将其打印出来。 - Peter Lawrey

6
你读取简单文本文件使用DataInputStream是否有特别的原因?Java文档中提到,数据输入流允许应用以机器无关的方式从底层输入流中读取基本的Java数据类型。可能是你读取文件的方式导致EOF被发送到输出流,在字符串到达之前导致管道结束。你需求似乎只是简单地读取文件并将其附加到wkhtmltoimage进程之前传递它。同时,您还缺少一个关闭输出流到进程的语句。这会导致进程等待(挂起),直到它从输入流获得EOF,而这永远不会发生。我建议使用BufferedReader,直接将其写入到输出流中,并在附加额外的字符串之前调用close()方法来关闭流。
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();
}

3

" - "中删除空格--正常的空格被shell解析器移除,但是在ProcessBuilder中,它被解释为文件名以空格开头和结尾(字面值)。

(实际上,像Peter建议的那样查看进程的输出可能会告诉你...)


3
创建 Process 对象后,您可以调用 getOutputStream() 方法来获取一个流,该流将其内容发送到进程的标准输入。
一旦您拥有了这个流,就可以使用Java的标准IO向此流中写入任何字节(包括将其包装在Writer中、添加缓冲等)-并且在写入它们时,只要它们被刷新,进程就会立即读取它们。

我正在使用 getOutputStream 方法,并从缓冲区向进程写入数据。这样做是否正确?我已经更新了问题。 - Hrishikesh Choudhari

1
以下代码同样有效:
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();
    }

}

这里的stdinecho.cpp是一个程序,它会输出在其提示符上输入的行:

#include <iostream>
#include <fstream>
#include <cstdio>

using namespace std;

int main()
{
    string strOutput;
    string str;
    while(getline(cin, str)) {
        cout << str << endl;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接