Java Runtime.getRuntime (): obteniendo resultados de la ejecución de un programa de línea de comandos

155

Estoy usando el tiempo de ejecución para ejecutar comandos del símbolo del sistema desde mi programa Java. Sin embargo, no sé cómo puedo obtener el resultado que devuelve el comando.

Aquí está mi código:

Runtime rt = Runtime.getRuntime();

String[] commands = {"system.exe", "-send" , argument};

Process proc = rt.exec(commands);

Intenté hacerlo System.out.println(proc);pero eso no me devolvió nada. La ejecución de ese comando debería devolver dos números separados por punto y coma. ¿Cómo podría obtener esto en una variable para imprimir?

Aquí está el código que estoy usando ahora:

String[] commands = {"system.exe", "-get t"};

Process proc = rt.exec(commands);

InputStream stdIn = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdIn);
BufferedReader br = new BufferedReader(isr);

String line = null;
System.out.println("<OUTPUT>");

while ((line = br.readLine()) != null)
     System.out.println(line);

System.out.println("</OUTPUT>");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);

Pero no obtengo nada como mi salida, pero cuando ejecuto ese comando, funciona bien.

usuario541597
fuente

Respuestas:

244

Aquí está el camino a seguir:

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-get t"};
Process proc = rt.exec(commands);

BufferedReader stdInput = new BufferedReader(new 
     InputStreamReader(proc.getInputStream()));

BufferedReader stdError = new BufferedReader(new 
     InputStreamReader(proc.getErrorStream()));

// Read the output from the command
System.out.println("Here is the standard output of the command:\n");
String s = null;
while ((s = stdInput.readLine()) != null) {
    System.out.println(s);
}

// Read any errors from the attempted command
System.out.println("Here is the standard error of the command (if any):\n");
while ((s = stdError.readLine()) != null) {
    System.out.println(s);
}

Lea el Javadoc para más detalles aquí . ProcessBuilderSería una buena opción para usar.

Senthil
fuente
44
@AlbertChen pwd && lsno solo ejecuta un solo archivo, cuando lo hace en un shell, ejecuta tanto los ejecutables /bin/pwdcomo los /bin/lsejecutables. Si quieres hacer cosas así dentro de Java, tendrás que hacer algo así {"/bin/bash","-c", "pwd && ls"}. Probablemente ya no tenga la pregunta, pero otras personas sí, así que pensé que podría responderla.
735Tesla
3
Creo que la lectura de las dos corrientes deben estar ocurriendo simultáneamente, porque si, como en su caso, la salida de stdStream llenará la memoria intermedia, que no será capaz de leer el flujo de error ..
Li3ro
3
Li3ro está parcialmente en lo cierto. El programa que está escuchando tiene un búfer stdouty stderrsalida limitados . Si no los escucha al mismo tiempo, uno de ellos se llenará mientras lee el otro. El programa que está escuchando bloqueará el intento de escribir en el búfer lleno, mientras que en el otro extremo su programa bloqueará el intento de leer desde un búfer que nunca volverá EOF. Usted debe leer de ambas corrientes al mismo tiempo.
Gili
1
@Gili Entonces, ¿por qué Li3ro está "parcialmente" en lo cierto? ¿No es Li3ro completa y completamente correcta? En este caso, no entiendo por qué hay una respuesta incorrecta por aquí desde 2011 y por qué tiene más de 200 votos a favor ... Eso es confuso.
Andrey Tyukin
2
@AndreyTyukin Tienes razón. Todas las respuestas actuales son vulnerables a los puntos muertos. Les animo a que les den un voto negativo para permitir que otras respuestas ganen visibilidad. Publiqué una nueva respuesta para su revisión: stackoverflow.com/a/57949752/14731 . Espero haber entendido bien ...
Gili
68

Una forma más rápida es esta:

public static String execCmd(String cmd) throws java.io.IOException {
    java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}

Que es básicamente una versión condensada de esto:

public static String execCmd(String cmd) throws java.io.IOException {
    Process proc = Runtime.getRuntime().exec(cmd);
    java.io.InputStream is = proc.getInputStream();
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    String val = "";
    if (s.hasNext()) {
        val = s.next();
    }
    else {
        val = "";
    }
    return val;
}

Sé que esta pregunta es antigua, pero estoy publicando esta respuesta porque creo que puede ser más rápido.

735Tesla
fuente
44
Gracias por la buena respuesta. ¿Por qué es "\\ A" el delimitador?
Gottfried
1
No recuerdo completamente cuál era mi lógica cuando originalmente escribí esto. He estado usando esta solución durante un tiempo, pero creo que fue porque \Aen una expresión regular significa el comienzo de la cadena y tuve que escapar de la barra.
735Tesla
55
"\ A" es el carácter de campana. "^" es el comienzo de una cadena en expresiones regulares, y "$" es el final de una cadena en expresiones regulares. Este es un personaje que esperarías no ver. El delimitador predeterminado es el espacio en blanco, de acuerdo con la documentación de Java, por lo que hacer esto probablemente escupiría el resultado completo del comando.
Hank Schultz
11

Además de utilizar ProcessBuilderSenthil como se sugiere, asegúrese de leer e implementar todas las recomendaciones de When Runtime.exec () no lo hará .

Andrew Thompson
fuente
Ese fragmento no parece estar consumiendo el flujo de error estándar (como se recomienda en el artículo vinculado). Tampoco está utilizando un ProcessBuildercomo ahora se recomienda dos veces. Usando a ProcessBuilder, es posible fusionar los flujos de salida y error para facilitar el consumo de ambos a la vez.
Andrew Thompson el
11

Si el uso ya tiene Apache commons-io disponible en el classpath, puede usar:

Process p = new ProcessBuilder("cat", "/etc/something").start();
String stderr = IOUtils.toString(p.getErrorStream(), Charset.defaultCharset());
String stdout = IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
torbellino
fuente
7

También podemos usar flujos para obtener la salida del comando:

public static void main(String[] args) throws IOException {

        Runtime runtime = Runtime.getRuntime();
        String[] commands  = {"free", "-h"};
        Process process = runtime.exec(commands);

        BufferedReader lineReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        lineReader.lines().forEach(System.out::println);

        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        errorReader.lines().forEach(System.out::println);
    }
Jackkobec
fuente
7

@Senthil y @Arend responden ( https://stackoverflow.com/a/5711150/2268559 ) mencionado ProcessBuilder. Aquí está el ejemplo que se usa ProcessBuildercon la especificación de variables de entorno y la carpeta de trabajo para el comando:

    ProcessBuilder pb = new ProcessBuilder("ls", "-a", "-l");

    Map<String, String> env = pb.environment();
    // If you want clean environment, call env.clear() first
    //env.clear();
    env.put("VAR1", "myValue");
    env.remove("OTHERVAR");
    env.put("VAR2", env.get("VAR1") + "suffix");

    File workingFolder = new File("/home/user");
    pb.directory(workingFolder);

    Process proc = pb.start();

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));

    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null)
        System.out.println(s);

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null)
        System.out.println(s);
nidalpres
fuente
6

Al momento de escribir esto, todas las otras respuestas que incluyen código pueden resultar en puntos muertos.

Los procesos tienen un búfer limitado stdouty stderrsalida. Si no los escucha al mismo tiempo, uno de ellos se llenará mientras intenta leer el otro. Por ejemplo, podría estar esperando leer stdoutmientras el proceso está esperando para escribir stderr. No puede leer desde el stdoutbúfer porque está vacío y el proceso no puede escribir en elstderr búfer porque está lleno. Cada uno se espera el uno al otro para siempre.

Aquí hay una posible forma de leer la salida de un proceso sin riesgo de puntos muertos:

public final class Processes
{
    private static final String NEWLINE = System.getProperty("line.separator");

    /**
     * @param command the command to run
     * @return the output of the command
     * @throws IOException if an I/O error occurs
     */
    public static String run(String... command) throws IOException
    {
        ProcessBuilder pb = new ProcessBuilder(command).redirectErrorStream(true);
        Process process = pb.start();
        StringBuilder result = new StringBuilder(80);
        try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
        {
            while (true)
            {
                String line = in.readLine();
                if (line == null)
                    break;
                result.append(line).append(NEWLINE);
            }
        }
        return result.toString();
    }

    /**
     * Prevent construction.
     */
    private Processes()
    {
    }
}

La clave es usar ProcessBuilder.redirectErrorStream(true)cuál redirigirá stderra la stdoutsecuencia. Esto le permite leer una sola secuencia sin tener que alternar entre stdouty stderr. Si desea implementar esto manualmente, deberá consumir las secuencias en dos subprocesos diferentes para asegurarse de que nunca bloquee.

Gili
fuente
¡Oh wow! No esperaba que respondieras el comentario tan rápido, ¡mucho menos responder esta pregunta eternamente vieja! :) Ahora estoy considerando comenzar una recompensa. Echaré un vistazo a tu respuesta más tarde. ¡Gracias!
Andrey Tyukin
1

Si escribe en Kotlin, puede usar:

val firstProcess = ProcessBuilder("echo","hello world").start()
val firstError = firstProcess.errorStream.readBytes().decodeToString()
val firstResult = firstProcess.inputStream.readBytes().decodeToString()
desarrollador de Android
fuente
0

Adaptado de la respuesta anterior:

public static String execCmdSync(String cmd, CmdExecResult callback) throws java.io.IOException, InterruptedException {
    RLog.i(TAG, "Running command:", cmd);

    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec(cmd);

    //String[] commands = {"system.exe", "-get t"};

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    StringBuffer stdOut = new StringBuffer();
    StringBuffer errOut = new StringBuffer();

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null) {
        System.out.println(s);
        stdOut.append(s);
    }

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null) {
        System.out.println(s);
        errOut.append(s);
    }

    if (callback == null) {
        return stdInput.toString();
    }

    int exitVal = proc.waitFor();
    callback.onComplete(exitVal == 0, exitVal, errOut.toString(), stdOut.toString(), cmd);

    return stdInput.toString();
}

public interface CmdExecResult{
    void onComplete(boolean success, int exitVal, String error, String output, String originalCmd);
}
Derek Zhu
fuente
0

Más o menos lo mismo que otros fragmentos en esta página, pero simplemente organizando las cosas sobre una función , aquí vamos ...

String str=shell_exec("ls -l");

La función de clase:

public String shell_exec(String cmd)
       {
       String o=null;
       try
         {
         Process p=Runtime.getRuntime().exec(cmd);
         BufferedReader b=new BufferedReader(new InputStreamReader(p.getInputStream()));
         String r;
         while((r=b.readLine())!=null)o+=r;
         }catch(Exception e){o="error";}
       return o;
       }
PYK
fuente
-1

Intenta leer InputStreamel tiempo de ejecución:

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-send", argument};
Process proc = rt.exec(commands);
BufferedReader br = new BufferedReader(
    new InputStreamReader(proc.getInputStream()));
String line;
while ((line = br.readLine()) != null)
    System.out.println(line);

Es posible que también deba leer la secuencia de error ( proc.getErrorStream()) si el proceso está imprimiendo una salida de error. Puede redirigir la secuencia de error a la secuencia de entrada si la usa ProcessBuilder.

brahmananda Kar
fuente