Java exec - execute system processes with Java ProcessBuilder and Process (part 2)

<< Back to "Java exec with ProcessBuilder and Process, part 1"

A complete Java class that executes a system command

Now that you've seen that snippet of code, here's a complete Java class named ProcessBuilderExample that can execute a Unix/Linux system command:

package com.devdaily.system;

import java.io.IOException;
import java.util.*;

public class ProcessBuilderExample
{
  
  public static void main(String[] args) throws Exception
  {
    new ProcessBuilderExample();
  }

  // can run basic ls or ps commands
  // can run command pipelines
  // can run sudo command if you know the password is correct
  public ProcessBuilderExample() 
  throws IOException, InterruptedException
  {
    // determine the number of processes running on the current
    // linux, unix, or mac os x system.
    List<String> commands = new ArrayList<String>();
    commands.add("/bin/sh");
    commands.add("-c");
    commands.add("ps aux | wc -l");

    SystemCommandExecutor commandExecutor = new SystemCommandExecutor(commands);
    int result = commandExecutor.executeCommand();

    // stdout and stderr of the command are returned as StringBuilder objects
    StringBuilder stdout = commandExecutor.getStandardOutputFromCommand();
    StringBuilder stderr = commandExecutor.getStandardErrorFromCommand();
    System.out.println("The numeric result of the command was: " + result);
    System.out.println("\nSTDOUT:");
    System.out.println(stdout);
    System.out.println("\nSTDERR:");
    System.out.println(stderr);
  }
}

As you can see from that code, the SystemCommandExecutor can throw two types of exceptions, and in your programs you should catch those exceptions and deal with them properly. (You'll see the potential causes of those exceptions shortly.)

Now that you've seen a couple of examples of how to write code to use the SystemCommandExecutor class, we can look at the source code for that class to see how it works behind the scenes.

Behind the 'java exec' curtain: ProcessBuilder, Process, and Thread

The SystemCommandExecutor constructor doesn't do anything exciting, so I'm skipping that for now. You can see it if you download the class (linked to at the end of this article).

As you've seen, this class has a method named executeCommand, and as of January 20, 2010, the executeCommand method looks like this:

public int executeCommand()
throws IOException, InterruptedException
{
  int exitValue = -99;

  try
  {
    // create the ProcessBuilder and Process
    ProcessBuilder pb = new ProcessBuilder(commandInformation);
    Process process = pb.start();

    // you need this if you're going to write something to the command's input stream
    // (such as when invoking the 'sudo' command, and it prompts you for a password).
    OutputStream stdOutput = process.getOutputStream();
    
    // i'm currently doing these on a separate line here in case i need to set them to null
    // to get the threads to stop.
    // see http://java.sun.com/j2se/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
    InputStream inputStream = process.getInputStream();
    InputStream errorStream = process.getErrorStream();

    // these need to run as java threads to get the standard output and error from the command.
    // the inputstream handler gets a reference to our stdOutput in case we need to write
    // something to it, such as with the sudo command
    inputStreamHandler = new ThreadedStreamHandler(inputStream, stdOutput, adminPassword);
    errorStreamHandler = new ThreadedStreamHandler(errorStream);

    // TODO the inputStreamHandler has a nasty side-effect of hanging if the password is wrong; fix it
    inputStreamHandler.start();
    errorStreamHandler.start();

    // TODO a better way to do this?
    exitValue = process.waitFor();

    // TODO a better way to do this?
    inputStreamHandler.interrupt();
    errorStreamHandler.interrupt();
    inputStreamHandler.join();
    errorStreamHandler.join();
  }
  catch (IOException e)
  {
    // TODO deal with this here, or just throw it?
    throw e;
  }
  catch (InterruptedException e)
  {
    // generated by process.waitFor() call
    // TODO deal with this here, or just throw it?
    throw e;
  }
  finally
  {
    return exitValue;
  }
}

This code is still a bit sloppy, but I think there are enough comments in there to help you get an idea of what I'm doing in this method. In short, this class does the following:

  • Creates a ProcessBuilder from the List of Strings you supplied.
  • Creates a Process object from that ProcessBuilder.
  • Does some work to handle the stdin, stdout, and stderr of the command you are executing. Those streams are handled in special ThreadedStreamHandler threads.
  • Waits for the process to finish.

There is probably much more to say about this class -- besides the part where there are several TODO items -- but I'll leave it at that until anyone has any questions. Most importantly, as long as you don't need to run a sudo command, this class appears to work fine today, executing simple system commands like ls /tmp, as well as more complicated pipeline commands, like the ps command shown earlier.

Actually, there is one more thing to say. This class also has two convenience methods that you access the output from the system command you exec'd:

/**
 * Get the standard output (stdout) from the command you just exec'd.
 */
public StringBuilder getStandardOutputFromCommand()
{
  return inputStreamHandler.getOutputBuffer();
}

/**
 * Get the standard error (stderr) from the command you just exec'd.
 */
public StringBuilder getStandardErrorFromCommand()
{
  return errorStreamHandler.getOutputBuffer();
}

The way this class works, it buffers all the output (stdout and stderr) from the system command you execute, and then lets you retrieve the output when the command is finished running. It accomplishes this by reading the stdout and stderr streams in two separate threads that are instances of my ThreadedStreamHandler class. Let's take a look at that class -- and then you'll be ready to use this code in your own projects.

 

<< Back to "Java exec with ProcessBuilder and Process, part 1"

Next: Java exec with ProcessBuilder and Process, part 3 >>