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

104

Considérez le code suivant:

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

try {

    // Execute the command and wait for it to complete
    Process child = Runtime.getRuntime().exec(commandf);
    child.waitFor();

    // Print the first 16 bytes of its output
    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 cours depuis le shell, bien sûr, cela fonctionne comme prévu:

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

Les internets me disent que, étant donné que le comportement des tuyaux n'est pas multiplateforme, les esprits brillants qui travaillent dans l'usine Java produisant Java ne peuvent pas garantir que les tuyaux fonctionnent.

Comment puis-je faire ceci?

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

Comment puis-je faire en sorte que Java effectue le piping et la redirection lors de l'appel de commandes shell?

poundifdef
la source
Je le vois comme ceci: si vous le faites avec la gestion native des chaînes Java, vous êtes assuré de la portabilité de l'application Java sur toutes les plates-formes supportées par Java. OTOH, si vous le faites avec des commandes shell, il est plus facile de changer la langue de Java, mais 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 pense que votre raisonnement est un peu curieux.
Janus Troelsen
Dans le cas spécifique de quelque chose de simple comme command | grep foovous êtes beaucoup mieux avec juste d'exécuter commandet de filtrer nativement en Java. Cela rend votre code un peu plus complexe, mais vous réduisez également considérablement la consommation globale de ressources et la surface d'attaque.
tripleee

Réponses:

182

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

Le tuyau fait partie de la coque, vous pouvez donc également faire quelque chose comme ceci:

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

Process p = Runtime.getRuntime().exec(cmd);
Kaj
la source
@Kaj Et si vous vouliez ajouter des options à lsie ls -lrt?
kaustav datta
8
@Kaj Je vois que vous essayez d'utiliser -c pour spécifier une chaîne de commandes au shell, mais je ne comprends pas pourquoi vous devez en faire un tableau de chaînes au lieu d'une seule chaîne?
David Doria
2
Si quelqu'un recherche une androidversion ici, utilisez-la à la /system/bin/shplace
Nexen
25

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 du langage (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 peut-être 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 piped sous la forme d'un tableau 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 le tableau String fonctionne avec 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 l'analyse en premier (d'une manière que nous n'aimons pas). En revanche, lorsque exec reçoit un tableau de chaînes, il le transmet simplement au système d'exploitation sans l'analyser.

En fait, si nous prenons le temps d'une journée bien remplie et regardons le code source (à 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  [More ...] 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);
}
Tihamer
la source
Je vois le même comportement avec la redirection, c'est-à-dire le caractère «>». Cette analyse supplémentaire sur les tableaux de chaînes est éclairante
Kris
Vous n'avez pas besoin de prendre une journée en dehors de votre horaire chargé - il est écrit sous vos yeux docs / api / java / lang / Runtime.html # exec (java.lang.String)
gpasch
6

Créez un Runtime pour exécuter chacun des processus. Récupérez le OutputStream du premier Runtime et copiez-le dans le InputStream du second.

SJuan76
la source
1
Il vaut la peine de souligner que c'est beaucoup moins efficace qu'un tube os-natif, car vous copiez de la mémoire dans l'espace d'adressage du processus Java, puis revenez au processus suivant.
Tim Boudreau
0

La réponse acceptée par @Kaj est pour Linux. C'est l'équivalent pour Windows:

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