Comment appeler une commande shell Linux à partir de Java

93

J'essaie d'exécuter certaines commandes Linux à partir de Java en utilisant la redirection (> &) et les tubes (|). Comment Java peut-il invoquer cshou bashcommander?

J'ai essayé d'utiliser ceci:

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

Mais ce n'est pas compatible avec les redirections ou les tuyaux.

Narek
la source
2
catet cshn'ont rien à voir les uns avec les autres.
Bombe le
4
Je peux comprendre la question pour d'autres commandes, mais pour cat: pourquoi diable ne lis-tu pas simplement le fichier?
Atmocreations
8
Tout le monde se trompe la première fois: exec () de Java n'utilise pas le shell du système sous-jacent pour exécuter la commande (comme le souligne kts). La redirection et le piping sont des caractéristiques d'un vrai shell et ne sont pas disponibles depuis exec () de Java.
SteveD
stevendick: Merci beaucoup, j'avais des problèmes à cause de la redirection et de la tuyauterie!
Narek le
System.exit (0) n'est pas à l'intérieur de la vérification conditionnelle si le processus est terminé, il se terminera donc toujours sans générer d'erreurs. N'écrivez jamais des conditionnelles sans accolades, pour éviter exactement ce genre d'erreur.

Réponses:

96

exec n'exécute pas de commande dans votre shell

essayer

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

au lieu.

EDIT :: Je n'ai pas csh sur mon système, j'ai donc utilisé bash à la place. Ce qui suit a fonctionné pour moi

Process p = Runtime.getRuntime().exec(new String[]{"bash","-c","ls /home/XXX"});
KitsuneYMG
la source
@Narek. Désolé pour ça. Je l'ai corrigé en supprimant les "" supplémentaires. Apparemment, ils ne sont pas nécessaires. Je n'ai pas csh sur mon système, mais cela fonctionne avec bash.
KitsuneYMG
3
Comme d'autres l'ont mentionné, cela devrait être indépendant que vous ayez csh ou bash, n'est-ce pas?
Narek
@Narek. Ça devrait, mais je ne sais pas comment csh gère les arguments.
KitsuneYMG
1
Je vous remercie. Cela a fonctionné. En fait, j'ai utilisé "sh" au lieu de "csh".
Farshid
5
Attention: cette solution rencontrera très probablement le problème typique de son blocage car vous n'avez pas lu ses flux de sortie et d'erreurs. Par exemple: stackoverflow.com/questions/8595748/java-runtime-exec
Evgeni Sergeev
32

Utilisez ProcessBuilder pour séparer les commandes et les arguments au lieu d'espaces. Cela devrait fonctionner quel que soit le shell utilisé:

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
la source
Même après java.util. *; est correctement importé Je ne peux pas exécuter l'exemple ci-dessus.
Steve K
@Stephan J'ai mis à jour l'exemple de code ci-dessus pour supprimer les instructions de journalisation et les variables qui ont été déclarées en dehors du code collé, et il devrait maintenant compiler et s'exécuter. N'hésitez pas à essayer et dites-moi comment cela fonctionne.
Tim
15

S'appuyant sur l'exemple de @ Tim pour créer une méthode autonome:

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

    }
}

(L'exemple de test est une commande qui répertorie tous les fichiers d'un répertoire et de ses sous-répertoires, de manière récursive, dans l'ordre chronologique .)

Au fait, si quelqu'un peut me dire pourquoi j'ai besoin de quatre et huit contre-obliques, au lieu de deux et quatre, je peux apprendre quelque chose. Il y a un niveau de plus de se produire sans échapper que ce que je compte.

Edit: Je viens d'essayer ce même code sous Linux, et là, il s'avère que j'ai besoin de moitié moins de barres obliques inverses dans la commande de test! (C'est-à-dire: le nombre attendu de deux et quatre.) Maintenant, ce n'est plus seulement bizarre, c'est un problème de portabilité.

Evgeni Sergeev
la source
Vous devez poser votre question en tant que nouvelle question StackOverflow, et non dans le cadre de votre réponse.
vog le
Fonctionne avec deux et quatre sur OSX / Oracle java8. Il semble y avoir un problème avec votre environnement java d'origine (dont vous ne mentionnez pas la nature)
TheMadsen