Buscar..


Observaciones

Tenga en cuenta que la API recomienda que, a partir de la versión 1.5, la forma preferida de crear un proceso es utilizar ProcessBuilder.start() .

Otra observación importante es que el valor de salida producido por waitFor depende del programa / script que se ejecuta. Por ejemplo, los códigos de salida producidos por calc.exe son diferentes de notepad.exe .

Ejemplo simple (versión de Java <1.5)

Este ejemplo llamará a la calculadora de windows. Es importante notar que el código de salida variará de acuerdo con el programa / script que se está llamando.

package process.example;

import java.io.IOException;

public class App {

    public static void main(String[] args) {
        try {
            // Executes windows calculator
            Process p = Runtime.getRuntime().exec("calc.exe");

            // Wait for process until it terminates
            int exitCode = p.waitFor();

            System.out.println(exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Usando la clase ProcessBuilder

La clase ProcessBuilder facilita el envío de un comando a través de la línea de comandos. Todo lo que requiere es una lista de cadenas que conforman los comandos que se ingresarán. Simplemente llame al método start () en su instancia de ProcessBuilder para ejecutar el comando.

Si tiene un programa llamado Add.exe que toma dos argumentos y los agrega, el código se vería así:

List<String> cmds = new ArrayList<>();
cmds.add("Add.exe"); //the name of the application to be run
cmds.add("1"); //the first argument
cmds.add("5"); //the second argument

ProcessBuilder pb = new ProcessBuilder(cmds);

//Set the working directory of the ProcessBuilder so it can find the .exe
//Alternatively you can just pass in the absolute file path of the .exe
File myWorkingDirectory = new File(yourFilePathNameGoesHere);
pb.workingDirectory(myWorkingDirectory);

try {
    Process p = pb.start(); 
} catch (IOException e) {
    e.printStackTrace();
}

Algunas cosas para tener en mente:

  • La matriz de comandos debe ser una cadena String
  • Los comandos deben estar en el orden (en la matriz) que serían si hiciera la llamada al programa en la misma línea de comandos (es decir, el nombre del archivo .exe no puede ir después del primer argumento).
  • Al configurar el directorio de trabajo, debe pasar un objeto de archivo y no solo el nombre del archivo como una cadena

Bloqueo frente a llamadas no bloqueadas

En general, al realizar una llamada a la línea de comando, el programa enviará el comando y luego continuará su ejecución.

Sin embargo, es posible que desee esperar a que finalice el programa llamado antes de continuar con su propia ejecución (por ejemplo, el programa llamado escribirá los datos en un archivo y su programa necesita ese acceso para acceder a esos datos).

Esto se puede hacer fácilmente llamando al método waitFor() desde la instancia de Process devuelta.

Ejemplo de uso:

//code setting up the commands omitted for brevity...

ProcessBuilder pb = new ProcessBuilder(cmds);

try {
    Process p = pb.start();
    p.waitFor();
} catch (IOException e) {
    e.printStackTrack();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//more lines of code here...

ch.vorburger.exec

Lanzar procesos externos desde Java utilizando la API java.lang.ProcessBuilder sin formato directamente puede ser un poco engorroso. La biblioteca de Apache Commons Exec lo hace un poco más fácil. La biblioteca ch.vorburger.exec se extiende más allá de Commons Exec para que sea realmente conveniente:

 ManagedProcess proc = new ManagedProcessBuilder("path-to-your-executable-binary")
     .addArgument("arg1")
     .addArgument("arg2")
     .setWorkingDirectory(new File("/tmp"))
     .setDestroyOnShutdown(true)
     .setConsoleBufferMaxLines(7000)
     .build();

proc.start();
int status = proc.waitForExit();
int status = proc.waitForExitMaxMsOrDestroy(3000);
String output = proc.getConsole();

proc.startAndWaitForConsoleMessageMaxMs("started!", 7000);
// use service offered by external process...
proc.destroy();

Pitfall: Runtime.exec, Process y ProcessBuilder no entienden la sintaxis de shell

Los métodos Runtime.exec(String ...) y Runtime.exec(String) permiten ejecutar un comando como un proceso externo 1 . En la primera versión, proporciona el nombre del comando y los argumentos del comando como elementos separados de la cadena de cadenas, y el tiempo de ejecución de Java solicita al sistema de tiempo de ejecución del sistema operativo que inicie el comando externo. La segunda versión es aparentemente fácil de usar, pero tiene algunas trampas.

En primer lugar, aquí hay un ejemplo de uso exec(String) se usa de manera segura:

Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
    System.out.println("created the directory");
}

Espacios en las rutas

Supongamos que generalizamos el ejemplo anterior para que podamos crear un directorio arbitrario:

Process p = Runtime.exec("mkdir " + dirPath);
// ...

Esto funcionará normalmente, pero fallará si dirPath es (por ejemplo) "/ home / user / My Documents". El problema es que exec(String) divide la cadena en un comando y argumentos simplemente buscando espacios en blanco. La cadena de comando:

"mkdir /home/user/My Documents"

se dividirá en:

"mkdir", "/home/user/My", "Documents"

y esto hará que el comando "mkdir" falle porque espera un argumento, no dos.

Ante esto, algunos programadores intentan agregar citas alrededor de la ruta de acceso. Esto tampoco funciona:

"mkdir \"/home/user/My Documents\""

se dividirá en:

"mkdir", "\"/home/user/My", "Documents\""

Los caracteres de comillas dobles adicionales que se agregaron para intentar "citar" los espacios se tratan como cualquier otro carácter que no sea un espacio en blanco. De hecho, cualquier cosa que hagamos o escapemos de los espacios va a fallar.

La forma de solucionar este problema en particular es usar la sobrecarga exec(String ...) .

Process p = Runtime.exec("mkdir", dirPath);
// ...

Esto funcionará si dirpath incluye caracteres de espacio en blanco porque esta sobrecarga de exec no intenta dividir los argumentos. Las cadenas se pasan a la llamada del sistema exec sistema operativo tal como está.

Redireccionamiento, tuberías y otra sintaxis de shell.

Supongamos que queremos redireccionar la entrada o salida de un comando externo, o ejecutar una canalización. Por ejemplo:

Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");

o

Process p = Runtime.exec("find source -name *.java | xargs grep package");

(El primer ejemplo enumera los nombres de todos los archivos Java en el sistema de archivos, y el segundo imprime las declaraciones del package 2 en los archivos Java en el árbol "fuente").

Estos no van a funcionar como se esperaba. En el primer caso, el comando "buscar" se ejecutará con "2> / dev / null" como un argumento de comando. No se interpretará como una redirección. En el segundo ejemplo, el carácter de canalización ("|") y las obras siguientes se asignarán al comando "buscar".

El problema aquí es que los métodos exec y ProcessBuilder no comprenden ninguna sintaxis de shell. Esto incluye redirecciones, conductos, expansión variable, globbing, etc.

En algunos casos (por ejemplo, redirección simple) puede lograr fácilmente el efecto deseado utilizando ProcessBuilder . Sin embargo, esto no es cierto en general. Un enfoque alternativo es ejecutar la línea de comandos en un shell; por ejemplo:

Process p = Runtime.exec("bash", "-c", 
                         "find / -name *.java -print 2>/dev/null");

o

Process p = Runtime.exec("bash", "-c", 
                         "find source -name \\*.java | xargs grep package");

Pero tenga en cuenta que en el segundo ejemplo, necesitábamos escapar del carácter comodín ("*") porque queremos que el comodín sea interpretado por "buscar" en lugar de por el shell.

Los comandos de shell incorporados no funcionan

Supongamos que los siguientes ejemplos no funcionarán en un sistema con un shell similar a UNIX:

Process p = Runtime.exec("cd", "/tmp");     // Change java app's home directory

o

Process p = Runtime.exec("export", "NAME=value");  // Export NAME to the java app's environment

Hay un par de razones por las que esto no funciona:

  1. En "cd" y "exportar" los comandos son comandos incorporados en el shell. No existen como ejecutables distintos.

  2. Para que los shell construidos puedan hacer lo que se supone que deben hacer (por ejemplo, cambiar el directorio de trabajo, actualizar el entorno), deben cambiar el lugar donde reside ese estado. Para una aplicación normal (incluida una aplicación Java), el estado está asociado con el proceso de la aplicación. Así, por ejemplo, el proceso hijo que ejecutaría el comando "cd" no pudo cambiar el directorio de trabajo de su proceso "java" principal. De manera similar, un proceso exec no puede cambiar el directorio de trabajo para un proceso que lo sigue.

Este razonamiento se aplica a todos los comandos incorporados del shell.


1 - También puede usar ProcessBuilder , pero eso no es relevante hasta el punto de este ejemplo.

2 - Esto es un poco áspero y listo ... pero una vez más, las fallas de este enfoque no son relevantes para el ejemplo.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow