Process output from apache-commons exec

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

}

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.