Cómo invocar un comando de shell de Linux desde Java

93

Estoy tratando de ejecutar algunos comandos de Linux desde Java usando redirección (> &) y tuberías (|). ¿Cómo puede Java invocar csho bashcomandos?

Traté de usar esto:

Process p = Runtime.getRuntime().exec("shell command");

Pero no es compatible con redirecciones o canalizaciones.

Narek
fuente
2
caty cshno tienen nada que ver el uno con el otro.
Bombe
4
Puedo entender la pregunta para otros comandos, pero para gato: ¿por qué diablos no lees el archivo?
Atmocreations
8
Todo el mundo se equivoca la primera vez: exec () de Java no utiliza el shell del sistema subyacente para ejecutar el comando (como señala kts). La redirección y la canalización son características de un shell real y no están disponibles en exec () de Java.
SteveD
stevendick: Muchas gracias, estaba teniendo problemas debido a la redirección y la canalización.
Narek
System.exit (0) no está dentro de la verificación condicional si el proceso está terminado, por lo que siempre saldrá sin generar errores. Nunca escriba condicionales sin llaves, para evitar exactamente este tipo de error.

Respuestas:

96

exec no ejecuta un comando en su shell

tratar

Process p = Runtime.getRuntime().exec(new String[]{"csh","-c","cat /home/narek/pk.txt"});

en lugar.

EDITAR :: No tengo csh en mi sistema, así que usé bash en su lugar. Lo siguiente funcionó para mí

Process p = Runtime.getRuntime().exec(new String[]{"bash","-c","ls /home/XXX"});
KitsuneYMG
fuente
@Narek. Lo siento por eso. Lo arreglé eliminando los \ "adicionales. Aparentemente no son necesarios. No tengo csh en mi sistema, pero funciona con bash.
KitsuneYMG
3
Como otros han mencionado, esto debería ser independiente ya sea que tenga csh o bash, ¿no es así?
Narek
@Narek. Debería, pero no sé cómo csh maneja los argumentos.
KitsuneYMG
1
Gracias. Esto funcionó. En realidad, usé "sh" en lugar de "csh".
Farshid
5
Advertencia: es muy probable que esta solución se encuentre con el problema típico de que se cuelgue porque no leyó su salida y los flujos de error. Por ejemplo: stackoverflow.com/questions/8595748/java-runtime-exec
Evgeni Sergeev
32

Utilice ProcessBuilder para separar comandos y argumentos en lugar de espacios. Esto debería funcionar independientemente del shell utilizado:

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(final String[] args) throws IOException, InterruptedException {
        //Build command 
        List<String> commands = new ArrayList<String>();
        commands.add("/bin/cat");
        //Add arguments
        commands.add("/home/narek/pk.txt");
        System.out.println(commands);

        //Run macro on target
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.directory(new File("/home/narek"));
        pb.redirectErrorStream(true);
        Process process = pb.start();

        //Read output
        StringBuilder out = new StringBuilder();
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line = null, previous = null;
        while ((line = br.readLine()) != null)
            if (!line.equals(previous)) {
                previous = line;
                out.append(line).append('\n');
                System.out.println(line);
            }

        //Check result
        if (process.waitFor() == 0) {
            System.out.println("Success!");
            System.exit(0);
        }

        //Abnormal termination: Log command parameters and output and throw ExecutionException
        System.err.println(commands);
        System.err.println(out.toString());
        System.exit(1);
    }
}
Tim
fuente
Incluso después de java.util. *; se importa correctamente No puedo ejecutar el ejemplo anterior.
Steve K
@ Stephan He actualizado el código de ejemplo anterior para eliminar las declaraciones de registro y las variables que se declararon fuera del código pegado, y ahora debería compilarse y ejecutarse. Siéntase libre de intentarlo y hacerme saber cómo funciona.
Tim
15

Sobre la base del ejemplo de @ Tim para crear un método autónomo:

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class Shell {

    /** Returns null if it failed for some reason.
     */
    public static ArrayList<String> command(final String cmdline,
    final String directory) {
        try {
            Process process = 
                new ProcessBuilder(new String[] {"bash", "-c", cmdline})
                    .redirectErrorStream(true)
                    .directory(new File(directory))
                    .start();

            ArrayList<String> output = new ArrayList<String>();
            BufferedReader br = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
            String line = null;
            while ( (line = br.readLine()) != null )
                output.add(line);

            //There should really be a timeout here.
            if (0 != process.waitFor())
                return null;

            return output;

        } catch (Exception e) {
            //Warning: doing this is no good in high quality applications.
            //Instead, present appropriate error messages to the user.
            //But it's perfectly fine for prototyping.

            return null;
        }
    }

    public static void main(String[] args) {
        test("which bash");

        test("find . -type f -printf '%T@\\\\t%p\\\\n' "
            + "| sort -n | cut -f 2- | "
            + "sed -e 's/ /\\\\\\\\ /g' | xargs ls -halt");

    }

    static void test(String cmdline) {
        ArrayList<String> output = command(cmdline, ".");
        if (null == output)
            System.out.println("\n\n\t\tCOMMAND FAILED: " + cmdline);
        else
            for (String line : output)
                System.out.println(line);

    }
}

(El ejemplo de prueba es un comando que enumera todos los archivos de un directorio y sus subdirectorios, de forma recursiva, en orden cronológico ).

Por cierto, si alguien puede decirme por qué necesito cuatro y ocho barras invertidas allí, en lugar de dos y cuatro, puedo aprender algo. Hay un nivel más de ausencia de escape que el que estoy contando.

Editar: Acabo de probar este mismo código en Linux, ¡y resulta que necesito la mitad de barras invertidas en el comando de prueba! (Es decir: el número esperado de dos y cuatro). Ahora ya no es solo extraño, es un problema de portabilidad.

Evgeni Sergeev
fuente
Debe hacer su pregunta como una nueva pregunta de StackOverflow, no como parte de su respuesta.
vog
Funciona con dos y cuatro en OSX / Oracle java8. Parece que hay un problema con su entorno java original (cuya naturaleza no menciona)
TheMadsen