Cómo ejecutar un proceso del sistema con Java

Escrito por el , actualizado el .
java gnu-linux planeta-codigo
Enlace permanente Comentarios

Dada la popularidad de Java es difícil que no encontremos en el propio JDK o librería la funcionalidad que necesitamos y sino en algún comando del sistema de los muchos que tenemos a disposición en un sistema GNU/Linux. Esto nos da acceso a una gran cantidad de funcionalidades también desde los programas Java.

Java

GNU

Linux

Java de por sí incluye una amplia colección de clases con las funcionalidades principales que podamos necesitar, si no lo ofrece en la API es muy posible que haya una librería que lo proporcione. Pero en algún momento quizá se nos dé el caso que un comando del sistema devuelve la información o realiza la acción que necesitamos. Puede ser una un comando del sistema GNU/Linux, Windows o Mac OS X o un script en Java, Python, Ruby u otro lenguaje de programación. A través de las clase Process y ProcessBuilder nos es posible lanzar un proceso de cualquier comando del sistema y acceder a su entrada estándar, salida estándar y salida de error.

La clase Process representa el proceso que lancemos, tenemos dos formas de obtener un referencia esta clase, una con Runtime.getInstance().exec() y otra con la clase ProcessBuilder con ambas nos será posible establecer variables de entorno, el directorio de trabajo o redirigir la entrada y salida. Obtenida la referencia a una instancia de Process con el método getInputStream() leeremos la salida estándar del proceso, con getErrorInputStream() la salida de errores y con getOutputStream() podremos enviar contenido a la entrada estándar. Otros métodos de utilidad nos permitirán conocer si el proceso sigue vivo con isAlive(), obtener el código de salida con exitValue(), esperar a que termine con waitFor(). Finalmente con destroy() podemos terminar de forma abrupta el proceso.

No son de las novedades destacables de Java 8 pero desde esta versión se puede establecer una espera máxima a la terminación del proceso, las variables de entorno y el directorio de trabajo. Y más cambios pequeños pero muy útiles como estos habrá sido añadidos a otras clases de la API.

Con esto nos es posible hacer cualquier cosa que el sistema pueda hacer según los comandos que tenga instalados o puedan instalarse y esto significa que está al alcance de Java en un sistema GNU/Linux el poder de su linea de comandos. Por ejemplo, supongamos que queremos saber en un programa Java el tiempo que lleva iniciado un sistema que nos sería útil si queremos implementar algún tipo de métricas de monitorización con Spring Boot Actuator. Java no tiene en su API un método que proporcione esta información, sin embargo, en GNU/Linux podemos saber este dato usando el sistema de archivos virtual accesible en /proc, concretamente en el archivo /proc/uptime. Este archivo contiene dos números el primero es el que nos interesaría siendo el número de segundos transcurridos desde que el sistema se inició y el segundo el tiempo que ha permanecido en reposo. El segundo número en un sistema con un procesador con varios núcleos físicos o lógicos es probable que se más alto que el primero.

1
2
$ cat /proc/uptime 
1488.79 5666.60
uptime.sh

Este sería el programa Java para conocer el tiempo que un sistema GNU/Linux lleva arrancado. Primero se crea el proceso, se espera a que termine y se obtiene su código de salida, se obtiene la salida del proceso (entrada para el programa), se procesa el resultado y se imprimen los segundos que lleva el sistema iniciado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package io.github.picodotdev.blogbitix.javaprocess;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class Main {

    public static void main(String[] args) throws Exception {
        // Iniciar el proceso
        ProcessBuilder builder = new ProcessBuilder().command("cat", "/proc/uptime");
        Process process = builder.start();

        // Alternativa a ProcessBuilder
        //Process process = Runtime.getRuntime().exec(new String[] { "cat", "/proc/uptime" });

        // Esperar a que termine el proceso y obtener su valor de salida
        process.waitFor(10, TimeUnit.SECONDS);
        int value = process.exitValue();
        if (value != 0) {
            throw new Exception(MessageFormat.format("Código de salida con error (%d)", value));
        }

        // Obtener la salida del proceso
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
        String result = br.lines().collect(Collectors.joining("\n"));
        br.close();
        
        // Obtener el tiempo desde el inicio del sistema 
        String seconds = result.split(" ")[0];
        System.out.printf("Segundos desde el inicio del sistema: %.2f", new BigDecimal(seconds));
    }
}
Main.java
1
2
Segundos desde el inicio del sistema: 3115,77

Main.out

Ejecutar un comando del sistema nos da acceso a un nuevo mundo de posibilidades aunque si podemos es mejor tener disponible un API para invocar la funcionalidad que queremos en vez de una integración más frágil leyendo y escribiendo en la salida, de error y entrada del proceso. En el siguiente artículo basándome es esto comentaré cómo enviar un correo electrónico en Java firmado digitalmente con GPG, Obtener el ancho y alto, escalar y convertir a otro formato imágenes con Java, aplicando esto mismo podemos obtener el país y ciudad en base a la dirección IP en una aplicación web.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: