121 votes

Comment faire fonctionner les tuyaux avec Runtime.exec()?

Considérez le code suivant :

String commandf = "ls /etc | grep release";

try {

    // Exécuter la commande et attendre sa fin
    Process child = Runtime.getRuntime().exec(commandf);
    child.waitFor();

    // Imprimer les 16 premiers octets de sa sortie
    InputStream i = child.getInputStream();
    byte[] b = new byte[16];
    i.read(b, 0, b.length); 
    System.out.println(new String(b));

} catch (IOException e) {
    e.printStackTrace();
    System.exit(-1);
}

La sortie du programme est :

/etc:
adduser.co

Lorsque je l'exécute à partir du shell, bien sûr, cela fonctionne comme prévu :

poundifdef@parker:~/rabbit_test$ ls /etc | grep release
lsb-release

Les internautes me disent que, en raison du fait que le comportement des pàipes n'est pas interplateforme, les brillants esprits qui travaillent dans l'usine Java produisant Java ne peuvent garantir que les pàipes fonctionnent.

Comment puis-je faire cela ?

Je ne vais pas faire tout mon parsing en utilisant des constructions Java plutôt que grep et sed, car si je veux changer de langage, je serai obligé de réécrire mon code d'analyse dans ce langage, ce qui est totalement inacceptable.

Comment puis-je faire en sorte que Java utilise les pàipes et les redirections lors de l'appel de commandes shell ?

0 votes

Je le vois ainsi: Si vous le faites avec la manipulation de chaînes Java natives, vous êtes garanti de pouvoir rendre l'application Java portable sur toutes les plateformes prises en charge par Java. D'un autre côté, si vous le faites avec des commandes shell, il est plus facile de changer la langue de Java, mais cela ne fonctionnera que lorsque vous êtes sur une plate-forme POSIX. Peu de gens changent la langue de l'application plutôt que la plate-forme sur laquelle l'application s'exécute. C'est pourquoi je trouve votre raisonnement un peu curieux.

0 votes

Dans le cas spécifique de quelque chose de simple comme commande | grep foo, il est préférable d'exécuter simplement commande et de filtrer nativement en Java. Cela rend votre code un peu plus complexe, mais vous réduisez également de manière significative la consommation de ressources globale et la surface d'attaque.

208voto

Kaj Points 6802

Écrivez un script et exécutez le script au lieu de commandes séparées.

Pipe fait partie de la shell, donc vous pouvez aussi faire quelque chose comme ceci:

String[] cmd = {
"/bin/sh",
"-c",
"ls /etc | grep release"
};

Process p = Runtime.getRuntime().exec(cmd);

0 votes

@Kaj Et si vous vouliez ajouter des options à ls c'est-à-dire ls -lrt ?

8 votes

@Kaj Je vois que vous essayez d'utiliser -c pour spécifier une chaîne de commandes à l'invite de commandes, mais je ne comprends pas pourquoi vous devez en faire un tableau de chaînes au lieu d'une seule chaîne?

4 votes

Si quelqu'un cherche la version android ici, alors utilisez /system/bin/sh à la place.

26voto

Tihamer Points 41

J'ai rencontré un problème similaire sous Linux, sauf que c'était "ps -ef | grep someprocess".
Au moins avec "ls", vous avez un remplacement Java indépendant de la langue (bien que plus lent). Par exemple :

File f = new File("C:\\");
String[] files = f.listFiles(new File("/home/tihamer"));
for (String file : files) {
    if (file.matches(.*some.*)) { System.out.println(file); }
}

Avec "ps", c'est un peu plus difficile, car Java ne semble pas avoir d'API pour cela.

J'ai entendu dire que Sigar pourrait nous aider : https://support.hyperic.com/display/SIGAR/Home

La solution la plus simple, cependant, (comme l'a souligné Kaj) est d'exécuter la commande pipée en tant qu'array de chaînes. Voici le code complet :

try {
    String line;
    String[] cmd = { "/bin/sh", "-c", "ps -ef | grep export" };
    Process p = Runtime.getRuntime().exec(cmd);
    BufferedReader in =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
    while ((line = in.readLine()) != null) {
        System.out.println(line); 
    }
    in.close();
} catch (Exception ex) {
    ex.printStackTrace();
}

Quant à savoir pourquoi l'array de chaînes fonctionne avec un pipe, alors qu'une seule chaîne ne le fait pas... c'est l'un des mystères de l'univers (surtout si vous n'avez pas lu le code source). Je soupçonne que c'est parce que lorsque exec reçoit une seule chaîne, il la parse d'abord (d'une manière qui ne nous plaît pas). En revanche, lorsque exec reçoit un array de chaînes, il le passe simplement au système d'exploitation sans le parser.

En fait, si nous prenons un peu de temps hors de notre journée chargée pour regarder le code source (sur http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/Runtime.java#Runtime.exec%28java.lang.String%2Cjava.lang.String[]%2Cjava.io.File%29), nous trouvons que c'est exactement ce qui se passe :

public Process  [Plus ...] exec(String command, String[] envp, File dir) 
          throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");
    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}

0 votes

Je constate le même comportement avec la redirection, c'est-à-dire le caractère '>'. Cette analyse supplémentaire sur les tableaux de chaînes de caractères est éclairante.

0 votes

Vous n'avez pas besoin de prendre une journée de votre emploi du temps chargé - c'est écrit devant vos yeux docs/api/java/lang/Runtime.html#exec(java.lang.String)

6voto

SJuan76 Points 16867

Créez un Runtime pour exécuter chacun des processus. Obtenez OutputStream du premier Runtime et copiez-le dans InputStream du deuxième.

1 votes

Il vaut la peine de souligner que c'est beaucoup moins efficace qu'un tube natif de système d'exploitation, car vous copiez la mémoire dans l'espace d'adresse du processus Java, puis vous la renvoyez au processus suivant.

3voto

yelliver Points 2410

@Kaj réponse acceptée est pour linux. Voici l'équivalent pour Windows :

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X