Java Runtime.getRuntime (): obtenir la sortie de l'exécution d'un programme en ligne de commande

155

J'utilise le runtime pour exécuter des commandes d'invite de commande à partir de mon programme Java. Cependant, je ne sais pas comment je peux obtenir la sortie renvoyée par la commande.

Voici mon code:

Runtime rt = Runtime.getRuntime();

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

Process proc = rt.exec(commands);

J'ai essayé de faire System.out.println(proc);mais cela n'a rien retourné. L'exécution de cette commande doit renvoyer deux nombres séparés par un point-virgule. Comment pourrais-je obtenir cela dans une variable à imprimer?

Voici le code que j'utilise maintenant:

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);

Mais je n'obtiens rien comme sortie, mais quand j'exécute cette commande moi-même, cela fonctionne très bien.

user541597
la source

Réponses:

244

Voici le chemin à parcourir:

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);
}

Lisez le Javadoc pour plus de détails ici . ProcessBuilderserait un bon choix à utiliser.

Senthil
la source
4
@AlbertChen n'exécute pwd && lspas seulement un seul fichier, lorsque vous faites cela dans un shell, il exécute à la fois les exécutables /bin/pwdet /bin/ls. Si vous voulez faire des choses comme ça dans java, vous devrez faire quelque chose comme {"/bin/bash","-c", "pwd && ls"}. Vous n'avez probablement plus la question mais d'autres personnes pourraient le faire, alors j'ai pensé que je pourrais y répondre.
735Tesla
3
Je pense que la lecture des deux flux doit se faire simultanément car si, comme dans votre cas, la sortie de stdStream remplit le tampon, vous ne pourrez pas lire le flux d'erreur ..
Li3ro
3
Li3ro a partiellement raison. Le programme que vous écoutez a une mémoire tampon limitée pour stdoutet la stderrsortie. Si vous ne les écoutez pas simultanément, l'un d'eux se remplira pendant que vous lirez l'autre. Le programme que vous écoutez bloquera alors les tentatives d'écriture dans le tampon rempli, tandis qu'à l'autre extrémité, votre programme bloquera les tentatives de lecture à partir d'un tampon qui ne reviendra jamais EOF. Vous devez lire les deux flux simultanément.
Gili
1
@Gili Alors pourquoi Li3ro a-t-il "partiellement" raison? Li3ro n'est-il pas tout à fait juste et totalement correct? Dans ce cas, je ne comprends pas pourquoi une mauvaise réponse traîne ici depuis 2011 et pourquoi il y a plus de 200 votes positifs ... C'est déroutant.
Andrey Tyukin le
2
@AndreyTyukin Vous avez raison. Toutes les réponses actuelles sont vulnérables aux blocages. Je les encourage à les décliner pour permettre à d'autres réponses de gagner en visibilité. J'ai posté une nouvelle réponse pour votre avis: stackoverflow.com/a/57949752/14731 . J'espère que j'ai bien compris ...
Gili
68

Un moyen plus rapide est le suivant:

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() : "";
}

Ce qui est essentiellement une version condensée de ceci:

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;
}

Je sais que cette question est ancienne mais je poste cette réponse car je pense que cela peut être plus rapide.

735Tesla
la source
4
Merci pour la gentille réponse. Pourquoi "\\ A" est-il le délimiteur?
Gottfried
1
Je ne me souviens pas complètement de ma logique lorsque j'ai écrit ceci. J'utilise cette solution depuis un certain temps mais je pense que c'était parce que \Adans une regex signifie début de chaîne et j'ai dû échapper à la barre oblique.
735Tesla
5
"\ A" est le caractère de la cloche. "^" est le début d'une chaîne dans regex, et "$" est la fin d'une chaîne dans regex. C'est un personnage que vous vous attendez à ne pas voir. Le délimiteur par défaut est un espace, selon la documentation Java, donc cela cracherait probablement le résultat complet de la commande.
Hank Schultz
11

En plus d'utiliser ProcessBuildercomme suggéré Senthil, assurez-vous de lire et d'implémenter toutes les recommandations de When Runtime.exec () ne le fera pas .

Andrew Thompson
la source
Cet extrait de code ne semble pas consommer le flux d'erreur standard (comme recommandé dans l'article lié). Il n'utilise pas non plus un ProcessBuildercomme maintenant recommandé deux fois. En utilisant a ProcessBuilder, il est possible de fusionner les flux de sortie et d'erreur pour faciliter la consommation des deux à la fois.
Andrew Thompson
11

Si vous utilisez déjà Apache commons-io disponible sur le chemin de classe, vous pouvez utiliser:

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

Nous pouvons également utiliser des flux pour obtenir la sortie de commande:

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
la source
7

Réponse @Senthil et @Arend ( https://stackoverflow.com/a/5711150/2268559 ) mentionnée ProcessBuilder. Voici l'exemple utilisant ProcessBuilderavec la spécification des variables d'environnement et du dossier de travail pour la commande:

    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
la source
6

Au moment de la rédaction de cet article, toutes les autres réponses contenant du code peuvent entraîner des blocages.

Les processus ont une mémoire tampon limitée pour stdoutet la stderrsortie. Si vous ne les écoutez pas simultanément, l'un d'entre eux se remplira pendant que vous essayez de lire l'autre. Par exemple, vous pourriez être en attente de lecture stdoutpendant que le processus attend pour écrire stderr. Vous ne pouvez pas lire à partir du stdouttampon car il est vide et le processus ne peut pas écrire dans le stderrtampon car il est plein. Vous vous attendez l'un l'autre pour toujours.

Voici un moyen possible de lire la sortie d'un processus sans risque de blocage:

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 clé est d'utiliser ProcessBuilder.redirectErrorStream(true)ce qui redirigera stderrdans le stdoutflux. Cela vous permet de lire un seul flux sans avoir à alterner entre stdoutet stderr. Si vous souhaitez l'implémenter manuellement, vous devrez consommer les flux dans deux threads différents pour vous assurer de ne jamais bloquer.

Gili
la source
Oh wow! Je ne m'attendais pas à ce que vous répondiez si vite au commentaire, encore moins à cette question éternellement ancienne! :) J'envisage maintenant de commencer une prime. Jettera un œil à votre réponse plus tard. Merci!
Andrey Tyukin
1

Si vous écrivez sur Kotlin, vous pouvez utiliser:

val firstProcess = ProcessBuilder("echo","hello world").start()
val firstError = firstProcess.errorStream.readBytes().decodeToString()
val firstResult = firstProcess.inputStream.readBytes().decodeToString()
développeur android
la source
0

Adapté de la réponse précédente:

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
la source
0

À peu près la même chose que les autres extraits de cette page, mais il suffit d'organiser les choses sur une fonction , nous y voilà ...

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

La fonction Classe:

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
la source
-1

Essayez de lire le InputStreamdu runtime:

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);

Vous devrez peut-être également lire le flux d'erreur ( proc.getErrorStream()) si le processus imprime une sortie d'erreur. Vous pouvez rediriger le flux d'erreur vers le flux d'entrée si vous utilisez ProcessBuilder.

brahmananda Kar
la source