So rufen Sie einen Linux-Shell-Befehl von Java aus auf

92

Ich versuche, einige Linux-Befehle von Java aus mit Umleitung (> &) und Pipes (|) auszuführen. Wie kann Java aufrufen cshoder bashBefehle?

Ich habe versucht, dies zu verwenden:

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

Es ist jedoch nicht mit Umleitungen oder Pipes kompatibel.

Narek
quelle
2
catund cshhaben nichts miteinander zu tun.
Bombe
4
Ich kann die Frage für andere Befehle verstehen, aber für Katze: Warum zum Teufel liest du nicht einfach die Datei ein?
Atmocreations
8
Jeder versteht dies beim ersten Mal falsch - Javas exec () verwendet nicht die Shell des zugrunde liegenden Systems, um den Befehl auszuführen (wie kts hervorhebt). Die Umleitung und das Piping sind Merkmale einer echten Shell und nicht in Javas exec () verfügbar.
SteveD
stevendick: Vielen Dank, ich hatte Probleme wegen Umleitung und Rohrleitungen!
Narek
System.exit (0) befindet sich nicht in der bedingten Überprüfung, ob der Prozess abgeschlossen ist, und wird daher immer ohne Ausgabe von Fehlern beendet. Schreiben Sie niemals Bedingungen ohne geschweifte Klammern, um genau diese Art von Fehler zu vermeiden.

Antworten:

95

exec führt keinen Befehl in Ihrer Shell aus

Versuchen

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

stattdessen.

EDIT :: Ich habe kein csh auf meinem System, also habe ich stattdessen bash verwendet. Folgendes hat bei mir funktioniert

Process p = Runtime.getRuntime().exec(new String[]{"bash","-c","ls /home/XXX"});
KitsuneYMG
quelle
@ Narek. Das tut mir leid. Ich habe es behoben, indem ich die zusätzlichen "" entfernt habe. Anscheinend werden sie nicht benötigt. Ich habe kein csh auf meinem System, aber es funktioniert mit bash.
KitsuneYMG
3
Wie andere bereits erwähnt haben, sollte dies unabhängig sein, ob Sie csh oder bash haben, nicht wahr?
Narek
@ Narek. Es sollte, aber ich weiß nicht, wie csh mit Argumenten umgeht.
KitsuneYMG
1
Danke dir. Das hat funktioniert. Eigentlich habe ich "sh" anstelle von "csh" verwendet.
Farshid
5
Warnung: Bei dieser Lösung tritt sehr wahrscheinlich das typische Problem auf, dass sie hängt, weil Sie die Ausgabe- und Fehlerströme nicht gelesen haben. Zum Beispiel: stackoverflow.com/questions/8595748/java-runtime-exec
Evgeni Sergeev
32

Verwenden Sie ProcessBuilder, um Befehle und Argumente anstelle von Leerzeichen zu trennen. Dies sollte unabhängig von der verwendeten Shell funktionieren:

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
quelle
Auch nach java.util. *; ist richtig importiert Ich kann das obige Beispiel nicht ausführen.
Steve K
@Stephan Ich habe den obigen Beispielcode aktualisiert, um die Protokollierungsanweisungen und -variablen zu entfernen, die außerhalb des eingefügten Codes deklariert wurden. Er sollte jetzt kompiliert und ausgeführt werden. Probieren Sie es einfach aus und lassen Sie mich wissen, wie es funktioniert.
Tim
15

Aufbauend auf dem Beispiel von @ Tim, um eine eigenständige Methode zu erstellen:

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

    }
}

(Das Testbeispiel ist ein Befehl, der alle Dateien in einem Verzeichnis und seinen Unterverzeichnissen rekursiv in chronologischer Reihenfolge auflistet .)

Übrigens, wenn mir jemand sagen kann, warum ich dort vier und acht Backslashes anstelle von zwei und vier brauche, kann ich etwas lernen. Es gibt eine Ebene mehr Unausweichlichkeit als das, was ich zähle.

Bearbeiten: Habe gerade denselben Code unter Linux ausprobiert und dort stellt sich heraus, dass ich halb so viele Backslashes im Testbefehl benötige! (Das heißt: die erwartete Anzahl von zwei und vier.) Jetzt ist es nicht mehr nur seltsam, es ist ein Portabilitätsproblem.

Evgeni Sergeev
quelle
Sie sollten Ihre Frage als neue StackOverflow-Frage stellen, nicht als Teil Ihrer Antwort.
Vog
Funktioniert mit zwei und vier unter OSX / Oracle Java8. Es scheint, dass es ein Problem mit Ihrer ursprünglichen Java-Umgebung gibt (deren Natur Sie nicht erwähnen)
TheMadsen