Process output from apache-commons exec

北慕城南 提交于 2019-11-28 10:42:46

Don't pass a ByteArrayOutputStream to the PumpStreamHandler, use an implementation of the abstract class org.apache.commons.exec.LogOutputStream. From the javadoc:

The implementation parses the incoming data to construct a line and passes the complete line to an user-defined implementation.

Thus the LogOutputStram is preprocessing the output to give you the control of handling individual lines instead of the raw bytes. Something like this:

import java.util.LinkedList;
import java.util.List;
import org.apache.commons.exec.LogOutputStream;

public class CollectingLogOutputStream extends LogOutputStream {
    private final List<String> lines = new LinkedList<String>();
    @Override protected void processLine(String line, int level) {
        lines.add(line);
    }   
    public List<String> getLines() {
        return lines;
    }
}

Then after the blocking call to exec.execute your getLines() will have the standard out and standard error you are looking for. The ExecutionResultHandler is optional from the perspective of just executing the process, and collecting all the stdOut/stdErr into a list of lines.

What I don't get is why there is no need for a 'waitfor' style command here? Isn't it possible that there will be some time in which the buffer will be empty, exit the loop, and continue on while the process is still going? When I run it, this doesn't seem to be the case.

readLine blocks. That is, your code will wait until a line has been read.

PumpStreamHandler

from Documentation

Copies standard output and error of subprocesses to standard output and error of the parent process. If output or error stream are set to null, any feedback from that stream will be lost.

Based on James A Wilson's answer I created the helper class "Execute". It wraps his answer into a solution that also supplies the exitValue for convenience.

A single line is necessary to execute a command this way:

ExecResult result=Execute.execCmd(cmd,expectedExitCode);

The following Junit Testcase tests and shows how to use it:

Junit4 test case:

package com.bitplan.newsletter;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Test;

import com.bitplan.cmd.Execute;
import com.bitplan.cmd.Execute.ExecResult;

/**
 * test case for the execute class
 * @author wf
 *
 */
public class TestExecute {
     @Test
   public void testExecute() throws Exception {
     String cmd="/bin/ls";
     ExecResult result = Execute.execCmd(cmd,0);
     assertEquals(0,result.getExitCode());
     List<String> lines = result.getLines();
     assertTrue(lines.size()>0);
     for (String line:lines) {
         System.out.println(line);
     }
   }
}

Execute Java helper Class:

package com.bitplan.cmd;

import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.LogOutputStream;
import org.apache.commons.exec.PumpStreamHandler;

/**
 * Execute helper using apache commons exed
 *
 *  add this dependency to your pom.xml:
   <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-exec</artifactId>
            <version>1.2</version>
        </dependency>

 * @author wf
 *
 */
public class Execute {

    protected static java.util.logging.Logger LOGGER = java.util.logging.Logger
            .getLogger("com.bitplan.cmd");

    protected final static boolean debug=true;

    /**
     * LogOutputStream
     * http://stackoverflow.com/questions/7340452/process-output-from
     * -apache-commons-exec
     * 
     * @author wf
     * 
     */
    public static class ExecResult extends LogOutputStream {
        private int exitCode;
        /**
         * @return the exitCode
         */
        public int getExitCode() {
            return exitCode;
        }

        /**
         * @param exitCode the exitCode to set
         */
        public void setExitCode(int exitCode) {
            this.exitCode = exitCode;
        }

        private final List<String> lines = new LinkedList<String>();

        @Override
        protected void processLine(String line, int level) {
            lines.add(line);
        }

        public List<String> getLines() {
            return lines;
        }
    }

    /**
     * execute the given command
     * @param cmd - the command 
     * @param exitValue - the expected exit Value
     * @return the output as lines and exit Code
     * @throws Exception
     */
    public static ExecResult execCmd(String cmd, int exitValue) throws Exception {
        if (debug)
            LOGGER.log(Level.INFO,"running "+cmd);
        CommandLine commandLine = CommandLine.parse(cmd);
        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(exitValue);
        ExecResult result =new ExecResult();
        executor.setStreamHandler(new PumpStreamHandler(result));
        result.setExitCode(executor.execute(commandLine));
        return result;
    }

}

Its a very old thread but I had to use Apache Commons Exec and had to solve the same problem. I trust with last version of Apache Commons Exec published in 2014, below solution works well both with and without watchdog;

class CollectingLogOutputStream implements ExecuteStreamHandler {
private final List<String> lines = new LinkedList<String>();
public void setProcessInputStream(OutputStream outputStream) throws IOException 
{
}
//important - read all output line by line to track errors
public void setProcessErrorStream(InputStream inputStream) throws IOException {
    InputStreamReader isr = new InputStreamReader(inputStream);
    BufferedReader br = new BufferedReader(isr);
    String line="";
    while( (line = br.readLine()) != null){
        //use lines whereever you want - for now just print on console
        System.out.println("error:"+line);
    }
}
//important - read all output line by line to track process output
public void setProcessOutputStream(InputStream inputStream) throws IOException 
{
    InputStreamReader isr = new InputStreamReader(inputStream);
    BufferedReader br = new BufferedReader(isr);
    String line="";
    while( (line = br.readLine()) != null){
        //use lines whereever you want - for now just print on console
        System.out.println("output:"+line);
    }
  }

public void start() throws IOException {
}

public void stop() throws IOException {
}
}

Above class can be set as StreamHandler for the executor as below;

//set newly created class stream handler for the executor
executor.setStreamHandler(new CollectingLogOutputStream());

Complete code is available here; https://github.com/raohammad/externalprocessfromjava

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!