En este artículo mostraremos dos formas de ejecutar un comando Shell desde nuestra aplicación Java.
- El primero es usar la clase Runtime y llamar a su método exec.
- La segunda y más personalizable forma será crear y usar una instancia de ProcessBuilder.
Dependencia del sistema operativo
Antes de crear un nuevo proceso que ejecuta nuestro comando Shell, primero debemos determinar el sistema operativo en el que se está ejecutando nuestro JVM.
Eso es porque, en Windows, necesitamos ejecutar nuestro comando como argumento para el shell cmd.exe y en todos los demás sistemas operativos podemos emitir un shell estándar, llamado sh:
//Verificar si el Sistema operativo es windows boolean isWindows = System.getProperty("os.name") .toLowerCase().startsWith("windows");
Entradas y Salidas
Además, necesitamos una forma de conectarnos a los flujos de entrada y salida de nuestro proceso.
Al menos la salida debe ser consumida; de lo contrario, nuestro proceso no regresa con éxito, sino que se bloqueará.
private static class StreamGobbler implements Runnable { private InputStream inputStream; private Consumer<String> consumer; //Constructor de la clase public StreamGobbler(InputStream inputStream, Consumer<String> consumer) { this.inputStream = inputStream; this.consumer = consumer; } @Override public void run() { new BufferedReader(new InputStreamReader(inputStream)).lines() .forEach(consumer); } }
Como podemos observar, esta clase está implementando la interfaz Runnable, lo que significa que cualquier ejecutor podría ejecutarla.
Runtime.exec()
Una llamada de método a Runtime.exec() es una forma simple, aún no personalizable, de engendrar un nuevo subproceso.
En el siguiente ejemplo solicitaremos una lista de directorio de un directorio de inicio de usuario e imprimiéndolo en la consola:
String homeDirectory = System.getProperty("user.home"); Process process; //diferenciamos el sistema operativo if (isWindows) { process = Runtime.getRuntime() .exec(String.format("cmd.exe /c dir %s", homeDirectory)); } else { process = Runtime.getRuntime() .exec(String.format("sh -c ls %s", homeDirectory)); } StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println); Executors.newSingleThreadExecutor().submit(streamGobbler); int exitCode = process.waitFor(); assert exitCode == 0;
ProcessBuilder
Para la segunda implementación de nuestro problema informático, utilizaremos ProcessBuilder. Esto es preferible al enfoque Runtime porque podemos personalizar algunos detalles.
Por ejemplo, podemos:
- Cambiar el directorio de trabajo en el que se ejecuta nuestro comando de shell usando builder.directory ().
- Guardar la información que se genera en algún archivo o una base de datos.
- Establecer un mapa de clave-valor personalizado como entorno usando builder.environment ().
- Redirigir las secuencias de entrada y salida a reemplazos personalizados.
- Heredar ambos a las secuencias del proceso de JVM actual utilizando builder.inheritIO ().
ProcessBuilder builder = new ProcessBuilder(); //diferenciamos el sistema operativo if (isWindows) { builder.command("cmd.exe", "/c", "dir"); } else { builder.command("sh", "-c", "ls"); } builder.directory(new File(System.getProperty("user.home"))); Process process = builder.start(); StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println); Executors.newSingleThreadExecutor().submit(streamGobbler); int exitCode = process.waitFor(); assert exitCode == 0;
Conclusión
En este artículo pudimos ver dos formas diferentes de ejecutar comandos Shell en Java, y que su implementación es bastante sencilla.
Si se planea personalizar la ejecución del proceso y se quiere escalar las funciones del sistema, en RootNite te recomendamos el uso de ProcessBuilder.