Java 调用Runtime.exec时捕获标准输出

Java 调用Runtime.exec时捕获标准输出,java,shell,runtime,Java,Shell,Runtime,当在客户机上遇到网络问题时,我希望能够运行一些命令行,并通过电子邮件将结果发送给我自己 我发现Runtime.exec允许我执行任意命令,但以字符串形式收集结果更有趣 我意识到我可以将输出重定向到一个文件,然后从该文件中读取,但我的蜘蛛侠意识告诉我有一种更优雅的方法 建议?使用。调用start()后,您将获得一个对象,从中可以获得stderr和stdout流 更新:ProcessBuilder为您提供更多控制;你不必使用它,但我发现从长远来看更容易。特别是将stderr重定向到stdout的功能

当在客户机上遇到网络问题时,我希望能够运行一些命令行,并通过电子邮件将结果发送给我自己

我发现Runtime.exec允许我执行任意命令,但以字符串形式收集结果更有趣

我意识到我可以将输出重定向到一个文件,然后从该文件中读取,但我的蜘蛛侠意识告诉我有一种更优雅的方法

建议?

使用。调用start()后,您将获得一个对象,从中可以获得stderr和stdout流


更新:ProcessBuilder为您提供更多控制;你不必使用它,但我发现从长远来看更容易。特别是将stderr重定向到stdout的功能,这意味着您只需要接收一个流。

Runtime.exec()返回一个进程对象,您可以从中提取运行的任何命令的输出。

您需要捕获进程中的std out和std err。然后,您可以将std写入文件/邮件或类似文件


有关更多信息,请参阅,特别注意在不同线程中捕获标准输出/错误的
StreamGobbler
机制。这对于防止阻塞非常重要,如果您没有正确地执行,这将导致大量错误

使用Runtime.exec可以为您提供一个进程。您可以使用这些来获取此进程的标准输出,并通过StringBuffer(例如)将此输入流放入字符串。

使用,Maven使用它来执行所有外部进程

Commandline commandLine = new Commandline();
commandLine.setExecutable(executable.getAbsolutePath());

Collection<String> args = getArguments();

for (String arg : args) {
    Arg _arg = commandLine.createArg();
    _arg.setValue(arg);
}

WriterStreamConsumer systemOut = new WriterStreamConsumer(console);
WriterStreamConsumer systemErr = new WriterStreamConsumer(console);

returnCode = CommandLineUtils.executeCommandLine(commandLine, systemOut, systemErr, 10);
if (returnCode != 0) {
    // bad
} else {
    // good
}
Commandline命令行=新命令行();
setExecutable(executable.getAbsolutePath());
集合args=getArguments();
for(字符串arg:args){
Arg _Arg=commandLine.createArg();
_参数设置值(arg);
}
WriterStreamConsumer systemOut=新的WriterStreamConsumer(控制台);
WriterStreamConsumer systemErr=新的WriterStreamConsumer(控制台);
returnCode=CommandLineUtils.executeCommandLine(commandLine,systemOut,systemErr,10);
如果(返回代码!=0){
//坏的
}否则{
//好
}
中的实用程序类可以帮助您:

String output = new VerboseProcess(
  new ProcessBuilder("executable with output")
).stdout();
您需要的唯一依赖项是:

<dependency>
  <groupId>com.jcabi</groupId>
  <artifactId>jcabi-log</artifactId>
  <version>0.7.5</version>
</dependency>

com.jcabi
jcabi测井
0.7.5

这是我多年来一直使用的助手类。一个小班。它有JavaWorld streamgobbler类来修复JVM资源泄漏。不知道是否对JVM6和JVM7仍然有效,但不会造成伤害。助手可以读取输出缓冲区以供以后使用

import java.io.*;

/**
 * Execute external process and optionally read output buffer.
 */
public class ShellExec {
    private int exitCode;
    private boolean readOutput, readError;
    private StreamGobbler errorGobbler, outputGobbler;

    public ShellExec() { 
        this(false, false);
    }

    public ShellExec(boolean readOutput, boolean readError) {
        this.readOutput = readOutput;
        this.readError = readError;
    }

    /**
     * Execute a command.
     * @param command   command ("c:/some/folder/script.bat" or "some/folder/script.sh")
     * @param workdir   working directory or NULL to use command folder
     * @param wait  wait for process to end
     * @param args  0..n command line arguments
     * @return  process exit code
     */
    public int execute(String command, String workdir, boolean wait, String...args) throws IOException {
        String[] cmdArr;
        if (args != null && args.length > 0) {
            cmdArr = new String[1+args.length];
            cmdArr[0] = command;
            System.arraycopy(args, 0, cmdArr, 1, args.length);
        } else {
            cmdArr = new String[] { command };
        }

        ProcessBuilder pb =  new ProcessBuilder(cmdArr);
        File workingDir = (workdir==null ? new File(command).getParentFile() : new File(workdir) );
        pb.directory(workingDir);

        Process process = pb.start();

        // Consume streams, older jvm's had a memory leak if streams were not read,
        // some other jvm+OS combinations may block unless streams are consumed.
        errorGobbler  = new StreamGobbler(process.getErrorStream(), readError);
        outputGobbler = new StreamGobbler(process.getInputStream(), readOutput);
        errorGobbler.start();
        outputGobbler.start();

        exitCode = 0;
        if (wait) {
            try { 
                process.waitFor();
                exitCode = process.exitValue();                 
            } catch (InterruptedException ex) { }
        }
        return exitCode;
    }   

    public int getExitCode() {
        return exitCode;
    }

    public boolean isOutputCompleted() {
        return (outputGobbler != null ? outputGobbler.isCompleted() : false);
    }

    public boolean isErrorCompleted() {
        return (errorGobbler != null ? errorGobbler.isCompleted() : false);
    }

    public String getOutput() {
        return (outputGobbler != null ? outputGobbler.getOutput() : null);        
    }

    public String getError() {
        return (errorGobbler != null ? errorGobbler.getOutput() : null);        
    }

//********************************************
//********************************************    

    /**
     * StreamGobbler reads inputstream to "gobble" it.
     * This is used by Executor class when running 
     * a commandline applications. Gobblers must read/purge
     * INSTR and ERRSTR process streams.
     * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
     */
    private class StreamGobbler extends Thread {
        private InputStream is;
        private StringBuilder output;
        private volatile boolean completed; // mark volatile to guarantee a thread safety

        public StreamGobbler(InputStream is, boolean readStream) {
            this.is = is;
            this.output = (readStream ? new StringBuilder(256) : null);
        }

        public void run() {
            completed = false;
            try {
                String NL = System.getProperty("line.separator", "\r\n");

                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                String line;
                while ( (line = br.readLine()) != null) {
                    if (output != null)
                        output.append(line + NL); 
                }
            } catch (IOException ex) {
                // ex.printStackTrace();
            }
            completed = true;
        }

        /**
         * Get inputstream buffer or null if stream
         * was not consumed.
         * @return
         */
        public String getOutput() {
            return (output != null ? output.toString() : null);
        }

        /**
         * Is input stream completed.
         * @return
         */
        public boolean isCompleted() {
            return completed;
        }

    }

}
下面是一个从.vbs脚本读取输出的示例,但类似的方法适用于linux sh脚本

   ShellExec exec = new ShellExec(true, false);
   exec.execute("cscript.exe", null, true,
      "//Nologo",
      "//B",            // batch mode, no prompts
      "//T:320",        // timeout seconds
      "c:/my/script/test1.vbs",  // unix path delim works for script.exe
      "script arg 1",
      "script arg 2",
   );
   System.out.println(exec.getOutput());

对于不产生太多输出的流程,我认为利用以下简单解决方案就足够了:

Process p = Runtime.getRuntime().exec("script");
p.waitFor();
String output = IOUtils.toString(p.getInputStream());
String errorOutput = IOUtils.toString(p.getErrorStream());
警告:但是,如果您的流程生成了大量输出,则此方法可能会导致问题,如中所述:

创建的子流程没有自己的终端或控制台。其所有标准io(即stdin、stdout、stderr)操作将通过三个流(getOutputStream()、getInputStream()、getErrorStream())重定向到父进程。父进程使用这些流向子进程提供输入并从中获取输出。由于某些本机平台仅为标准输入和输出流提供有限的缓冲区大小,因此未能及时写入子流程的输入流或读取子流程的输出流可能会导致子流程阻塞,甚至死锁


当核心语言有一个完全合适的替代方案时,为什么要使用外部库呢?有一些差异在一开始可能看不到。1.如果调用Process.waitFor(),它将阻塞,这意味着您必须读取进程输出,否则进程将等待输出缓冲区(控制台输出)可用。如果选择此路径(自己获取输出),则不能使用waitFor()。2.如果进行轮询,则必须在等待读取输出时添加自己的代码来处理。像Plexus Utils-246k这样的库的目的是帮助您避免重新发明轮子:)Ant做同样的事情,如果您愿意,您可以使用它,有一个核心任务可以调用(使用适当的初始化Ant上下文)来执行此任务,但我更喜欢Plexus Utils,因为它更小(您甚至可以去掉除cli软件包之外的所有内容,这意味着您将拥有不到50k的软件包)、专用和可靠的稳定性(因为它包含在Maven 2中)如何从
OutputStream
获取输出?我注意到,如果命令给出大量输出,那么在gobbler完成输出之前,代码流将继续。需要调用.join()在返回之前对其进行初始化。非常有效且简单的方法。但需要注意的是,主方法中的
cmd
数组初始化对于Windows 7来说似乎有点过时。添加最后一个“else”子句,该子句将cmd数组初始化为NT样式,即使另一个
else if(osName.equals(“Windows NT”)
返回错误。SteamGobbler的解决方案是否仅适用于Windows服务器?如果我使用Unix,则不会发生这种情况?在Windows中,它是command.exe或cmd.exe。如果使用Linux,则必须指定您选择的命令解释器,如bash等。@Odelya-我不相信这是操作系统特有的。请看一看。