【问题标题】:Java - Use Input and OutputStream of ProcessBuilder continuouslyJava - 持续使用 ProcessBuilder 的 Input 和 OutputStream
【发布时间】:2015-01-12 15:17:01
【问题描述】:

我想在提取一些数据时使用外部工具(通过行循环)。 为此,我首先使用 Runtime.getRuntime().exec() 来执行它。 但后来我的提取变得非常缓慢。所以我正在寻找一种可能性,在循环的每个实例中执行外部工具,使用相同的 shell 实例。

我发现,我应该使用 ProcessBuilder。但它还没有工作。

这是我测试执行的代码(已经从论坛中的答案输入):

public class ExecuteShell {
   ProcessBuilder builder;
   Process process = null;
   BufferedWriter process_stdin;
   BufferedReader reader, errReader;

   public ExecuteShell() {
    String command;
    command = getShellCommandForOperatingSystem();
    if(command.equals("")) {
        return; //Fehler!  No error handling yet
    }
    //init shell
    builder = new ProcessBuilder( command);
    builder.redirectErrorStream(true);
    try {
        process = builder.start();
    } catch (IOException e) {
        System.out.println(e);
    }

    //get stdout of shell  
    reader    = new BufferedReader(new InputStreamReader(process.getInputStream()));  
    errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));

    //get stdin of shell
    process_stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
    System.out.println("ExecuteShell: Constructor successfully finished");
   }

   public String executeCommand(String commands) {
    StringBuffer output;
    String line;
    try {
        //single execution
        process_stdin.write(commands);
        process_stdin.newLine();
        process_stdin.flush();
    } catch (IOException e) {
        System.out.println(e);
    }
    output    = new StringBuffer();
    line = ""; 

    try {
        if (!reader.ready()) {
            output.append("Reader empty \n");
            return output.toString();
        }
        while ((line = reader.readLine())!= null) {
            output.append(line + "\n");
            return output.toString();
        }
        if (!reader.ready()) {
            output.append("errReader empty \n");
            return output.toString();
        }
        while ((line = errReader.readLine())!= null) {
            output.append(line + "\n");
        }
    } catch (Exception e) {
        System.out.println("ExecuteShell: error in executeShell2File");
        e.printStackTrace();
        return "";
    }
    return output.toString();
   }


   public int close() {
    // finally close the shell by execution exit command
    try {
        process_stdin.write("exit");
        process_stdin.newLine();
        process_stdin.flush();
    }
    catch (IOException e) {
        System.out.println(e);
        return 1;
    }
    return 0;
   }

   private static String getShellCommandForOperatingSystem() {
    Properties prop = System.getProperties( );
    String os =  prop.getProperty( "os.name" );
    if ( os.startsWith("Windows") ) {
        //System.out.println("WINDOWS!");
        return "C:/cygwin64/bin/bash";
    } else if (os.startsWith("Linux") ) { 
        //System.out.println("Linux!");
        return"/bin/sh";
    }
    return "";      
   }
}

我想在另一个类中调用它,比如这个 Testclass:

public class TestExec{
    public static void main(String[] args) {
        String result = "";
        ExecuteShell es = new ExecuteShell();
        for (int i=0; i<5; i++) {
          // do something
          result = es.executeCommand("date"); //execute some command
          System.out.println("result:\n" + result); //do something with result
          // do something
        }
        es.close();
    }
}

我的问题是,输出流总是空的:

ExecuteShell: Constructor successfully finished
result:
Reader empty 

result:
Reader empty 

result:
Reader empty 

result:
Reader empty 

result:
Reader empty 

我在这里阅读了帖子:Java Process with Input/Output Stream

但是代码 sn-ps 不足以让我继续前进,我错过了一些东西。我并没有真正使用不同的线程。而且我不确定扫描仪是否/如何对我有任何帮助。非常感谢您的帮助。

最终,我的目标是重复调用外部命令并使其快速。

编辑: 我改变了循环,使 es.close() 在外面。我想补充一点,我不希望只有这个在循环中。

编辑: 时间的问题是,我调用的命令导致了错误。当命令没有报错时,时间是可以接受的。

感谢您的回答

【问题讨论】:

    标签: java processbuilder


    【解决方案1】:

    您可能遇到了竞争情况:将命令写入 shell 后,您的 Java 程序继续运行,并且几乎立即调用 reader.ready()。你想要执行的命令可能还没有输出任何东西,所以阅读器没有可用的数据。另一种解释是该命令不会向 stdout 写入任何内容,而只会向 stderr (或 shell,也许它无法启动命令?)。但是,您实际上并没有从 stderr 中读取数据。

    要正确处理输出和错误流,您不能检查reader.ready(),但需要在循环中调用readLine()(等待数据可用)。使用您的代码,即使程序会到达该点,您也只能从输出中读取一行。如果程序输出多于一行,则该数据将被解释为下一个命令的输出。典型的解决方案是循环读取直到readLine()返回null,但这在这里不起作用,因为这意味着你的程序将在这个循环中等待直到shell终止(这永远不会发生,所以它只会挂起无限地)。 如果您不知道每个命令将写入 stdout 和 stderr 的确切行数,那么解决此问题几乎是不可能的。

    但是,您使用 shell 并向其发送命令的复杂方法可能完全没有必要。从 Java 程序和 shell 中启动命令同样快,而且更容易编写。同样,Runtime.exec()ProcessBuilder 之间没有性能差异(前者只是调用后者),如果您需要它的高级功能,则只需要ProcessBuilder

    如果您在调用外部程序时遇到性能问题,您应该找出它们的确切位置并尝试解决它们,但不要使用这种方法。例如,通常启动一个线程来读取输出和错误流(如果您不启动单独的线程并且该命令产生大量输出,则一切都可能挂起)。这可能会很慢,因此您可以使用线程池来避免重复产生进程。

    【讨论】:

    • 补充:一般最好不要用Java代替bash。
    • 您好菲利普,感谢您的回答。当我没有检查阅读器是否准备好时。根本没有回应(我什至等了几分钟......)。这就是我在 while 循环中添加 return 语句的原因。因为我首先想到的可能是它挂在那里(当我在这里发布我的代码时我只是忘记了它)。我只是没有得到外壳的输出。我认为命令还没有真正开始。 “从 Java 程序和 shell 中启动命令同样快,而且更容易编写”是什么意思?你的意思是 bash 脚本而不是 java 程序?
    • 我的意思是你根本不应该打扰shell,而是直接使用Runtime.exec()ProcessBuilder运行你想要执行的命令(在你的情况下是date)。
    • 我想调用 cs2cs,因此需要在我的命令中包含一个管道或 stackoverflow.com/questions/11336157/…(与上述相同的问题。程序挂起,没有输出)
    • 如果你需要一个管道 (|),你真的需要一个 shell。但是,我建议每次都使用 /bin/sh -c 'command | command' 之类的东西启动一个新的 shell。除了命令之外,启动 shell 的开销应该可以忽略不计,它让你的事情变得更容易。
    猜你喜欢
    • 2019-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多