Skip to content

2 ProcessBuilder

Creación de subprocesos con ProcessBuilder

A partir de la versión Java 5, el método recomendado para crear un proceso desde nuestro programa consiste en crear un objeto de la clase ProcessBuilder (lanzador de procesos), cuyo constructor ProcessBuilder(commandWords...), o ProcessBuilder(commandWordsList), recibe una lista de cadenas con los atributos necesarios para iniciar un proceso (no pueden contener espacios).

Una vez creado el objeto ProcessBuilder, podemos lanzar la ejecución del subproceso llamando a su método start(), que, al igual que el método exec() de Runtime, nos retornará un objeto de la clase Process que representa el subproceso hijo recién iniciado.

ArrayList<String> command = new ArrayList<>(Arrays.asList("CMD", "/C", "DIR"));
ProcessBuilder processBuilder = new ProcessBuilder(command);
Process process = processBuilder.start();

El método environment() de la clase ProcessBuilder retorna un mapa Map<String, String> donde cada par corresponde a una variable de entorno, donde la clave es el nombre de la variable. El mapa inicialmente contendrá una copia del valor de las variables de entorno del proceso actual, correspondiente al valor retornado por System.getenv().

El método directory(file) permite establecer el directorio de trabajo del proceso.

ProcessBuilder processBuilder =
      new ProcessBuilder("/bin/sh", "-c", "echo $horse $dog $HOME");
processBuilder.environment().put("horse", "oats");
processBuilder.environment().put("dog", "treats");
Process process = pb.start()

Obtener información sobre el subproceso

Desde el punto de visto de Java un proceso (o subproceso) es representado por la clase Process. Esta clase ofrece distintos métodos informativos sobre el proceso que representa:

  • pid(): Retorna al process ID (PID) que el sistema operativo le ha asignado al proceso. Disponible a partir de Java 9.
  • info(): Retorna un objeto de la clase ProcessHandle.Info con información sobre el proceso. Disponible a partir de Java 9.
  • isAlive(): Retorna un booleano indicativo de si el proceso está vivo, es decir, no ha terminado aún su ejecución.
  • exitValue(): Cuando un proceso termina su ejecución puede indicar un valor indicativo de si todo ha ido bien o no. El método exitValue() retorna el valor que indicó el proceso como valor de terminación, que normalmente corresponde al valor 0 si todo ha ido bien.
  • children: Retorna un Stream<ProcessHandle>, un stream de objetos ProcessHandle asociados a los procesos que sean hijos directos del proceso, es decir que hayan sido creados directamente por él. Disponible a partir de Java 9.
  • descendants(): Similar al anterior, pero retorna un stream de todos los subprocesos del proceso, incluyendo los que no hayan sido creados directamente por él, sino por sus hijos, nietos, etc. Disponible a partir de Java 9.

Como acabamos de comentar, el método info() de la clase Process retorna un objeto de la clase ProcessHandler.Info, del que podemos extraer información adicional sobre el proceso, a través de sus métodos (disponibles a partir de Java 9):

  • arguments(): Retorna un Optional<String[]> con los argumentos del comando con el que se inició el subproceso.
  • command(): Retorna un Optional<String> con el nombre del comando con el que se inició el subproceso.
  • commandLine(): Retorna un Optional<String> con la línea de comandos con la que se inició el subproceso.
  • startInstant(): Retorna un Optional<Instant> con el instante de inicio del subproceso.
  • totalCpuDuration(): Retorna un Optional<Direction> con el tiempo total de CPU acumulado por el subproceso.
  • user(): Retorna un Optional<String> con el usuario del subproceso.

La disponibilidad de acceso a los atributos d un proceso varía dependiendo del sistema operativo y de los privilegios del proceso que esté realizando la consulta. Por este motivo, todos los métodos anteriores retornan un Optional<T>, por lo que deberemos siempre comprobar si realmente se nos ha proporcionado o no el valor.

Veamos un ejemplo:

void getInfoTest() throws IOException {
    ProcessBuilder processBuilder = new ProcessBuilder("echo", "Hello World!");
    String notAvailable = "<not available>";
    Process process = processBuilder.start();
    ProcessHandle.Info info = process.info();
    System.out.printf("Process ID: %s\n", process.pid());
    System.out.printf("Command name: %s\n", info.command().orElse(notAvailable));
    System.out.printf("Command line: %s\n", info.commandLine().orElse(notAvailable));
    System.out.printf("Start time: %s\n",
                      info.startInstant().map(i -> i.atZone(ZoneId.systemDefault())
                                              .toLocalDateTime().toString())
                      .orElse(notAvailable));
    System.out.printf("Arguments: %s\n",
                      info.arguments().map(arguments -> Stream.of(arguments)
                                           .collect(Collectors.joining(" ")))
                      .orElse(notAvailable));
    System.out.printf("User: %s\n", info.user().orElse(notAvailable));
}