Java-Prozess mit Eingabe- / Ausgabestream

90

Ich habe das folgende Codebeispiel unten. Wobei Sie einen Befehl in die Bash-Shell eingeben können, dh echo testdas Ergebnis zurückgeben lassen. Allerdings nach dem ersten Lesen. Andere Ausgabestreams funktionieren nicht?

Warum ist das so oder mache ich etwas falsch? Mein Endziel ist es, eine geplante Thread-Aufgabe zu erstellen, die regelmäßig einen Befehl für / bash ausführt, damit die OutputStreamund zusammenarbeiten InputStreammüssen und nicht aufhören zu arbeiten. Ich habe auch den Fehler java.io.IOException: Broken pipeirgendwelche Ideen erfahren ?

Vielen Dank.

String line;
Scanner scan = new Scanner(System.in);

Process process = Runtime.getRuntime ().exec ("/bin/bash");
OutputStream stdin = process.getOutputStream ();
InputStream stderr = process.getErrorStream ();
InputStream stdout = process.getInputStream ();

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

String input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.close();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}
James Moore
quelle
"Rohrbruch" bedeutet wahrscheinlich, dass der untergeordnete Prozess beendet wurde. Ich habe den Rest Ihres Codes nicht vollständig durchgesehen, um zu sehen, was die anderen Probleme sind.
Vanza
1
Verwenden Sie separate Threads, es wird gut funktionieren
Johnydep

Antworten:

139

Erstens würde ich empfehlen, die Leitung zu ersetzen

Process process = Runtime.getRuntime ().exec ("/bin/bash");

mit den Zeilen

ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();

ProcessBuilder ist neu in Java 5 und erleichtert das Ausführen externer Prozesse. Meiner Meinung nach besteht die wichtigste Verbesserung Runtime.getRuntime().exec()darin, dass Sie den Standardfehler des untergeordneten Prozesses in seine Standardausgabe umleiten können. Dies bedeutet, dass Sie nur einen InputStreamzum Lesen haben. Zuvor mussten zwei separate Threads vorhanden sein, ein Lesevorgang von stdoutund ein Lesevorgang von stderr, um zu vermeiden, dass der Standardfehlerpuffer gefüllt wird, während der Standardausgabepuffer leer ist (wodurch der untergeordnete Prozess hängen bleibt), oder umgekehrt.

Als nächstes die Schleifen (von denen Sie zwei haben)

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

Beenden Sie readerdas Programm nur, wenn das , das aus der Standardausgabe des Prozesses liest, das Dateiende zurückgibt. Dies geschieht nur, wenn der bashProzess beendet wird. Es wird kein Dateiende zurückgegeben, wenn derzeit keine Ausgabe mehr vom Prozess erfolgt. Stattdessen wartet es auf die nächste Ausgabezeile des Prozesses und kehrt erst zurück, wenn es diese nächste Zeile hat.

Da Sie zwei Eingabezeilen an den Prozess senden, bevor Sie diese Schleife erreichen, bleibt die erste dieser beiden Schleifen hängen, wenn der Prozess nach diesen beiden Eingabezeilen nicht beendet wurde. Es wird dort sitzen und darauf warten, dass eine andere Zeile gelesen wird, aber es wird nie eine andere Zeile geben, in der es gelesen werden kann.

Ich Ihren Quellcode kompiliert (Ich bin auf Windows zur Zeit, so dass ich ersetzt /bin/bashmit cmd.exe, aber die Prinzipien sollten gleich sein), und ich stellte fest , dass:

  • Nach dem Eingeben von zwei Zeilen wird die Ausgabe der ersten beiden Befehle angezeigt, aber dann hängt das Programm.
  • Wenn ich beispielsweise tippe echo testund dann exit, verlässt das Programm die erste Schleife seit dem cmd.exeBeenden des Prozesses. Das Programm fordert dann eine weitere Eingabezeile an (die ignoriert wird), überspringt direkt die zweite Schleife, da der untergeordnete Prozess bereits beendet wurde, und beendet sich dann selbst.
  • Wenn ich exitdann echo testtippe, erhalte ich eine IOException, die sich über das Schließen einer Pipe beschwert. Dies ist zu erwarten - die erste Eingabezeile hat den Prozess beendet, und die zweite Zeile kann nirgendwo gesendet werden.

Ich habe in einem Programm, an dem ich früher gearbeitet habe, einen Trick gesehen, der etwas Ähnliches bewirkt, wie Sie es zu wollen scheinen. Dieses Programm behielt eine Reihe von Shells bei, führte darin Befehle aus und las die Ausgabe dieser Befehle. Der verwendete Trick bestand darin, immer eine "magische" Zeile zu schreiben, die das Ende der Ausgabe des Shell-Befehls markiert, und damit zu bestimmen, wann die Ausgabe des an die Shell gesendeten Befehls beendet war.

Ich habe Ihren Code genommen und alles nach der zugewiesenen Zeile writerdurch die folgende Schleife ersetzt:

while (scan.hasNext()) {
    String input = scan.nextLine();
    if (input.trim().equals("exit")) {
        // Putting 'exit' amongst the echo --EOF--s below doesn't work.
        writer.write("exit\n");
    } else {
        writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n");
    }
    writer.flush();

    line = reader.readLine();
    while (line != null && ! line.trim().equals("--EOF--")) {
        System.out.println ("Stdout: " + line);
        line = reader.readLine();
    }
    if (line == null) {
        break;
    }
}

Danach konnte ich zuverlässig einige Befehle ausführen und die Ausgabe von jedem einzeln an mich zurücksenden lassen.

Die beiden echo --EOF--Befehle in der an die Shell gesendeten Zeile dienen dazu, sicherzustellen, dass die Ausgabe des Befehls --EOF--auch im Ergebnis eines Befehlsfehlers beendet wird.

Natürlich hat dieser Ansatz seine Grenzen. Diese Einschränkungen umfassen:

  • Wenn ich einen Befehl eingebe, der auf Benutzereingaben wartet (z. B. eine andere Shell), scheint das Programm zu hängen.
  • Es wird davon ausgegangen, dass jeder von der Shell ausgeführte Prozess seine Ausgabe mit einem Zeilenumbruch beendet.
  • Es wird etwas verwirrt, wenn der von der Shell ausgeführte Befehl zufällig eine Zeile ausschreibt --EOF--.
  • bashmeldet einen Syntaxfehler und wird beendet, wenn Sie Text mit einem nicht übereinstimmenden Text eingeben ).

Diese Punkte sind für Sie möglicherweise nicht von Bedeutung, wenn sich das, was Sie als geplante Aufgabe ausführen möchten, auf einen Befehl oder einen kleinen Satz von Befehlen beschränkt, die sich niemals so pathologisch verhalten werden.

BEARBEITEN : Verbessern Sie das Exit-Handling und andere kleinere Änderungen, nachdem Sie dies unter Linux ausgeführt haben.

Luke Woodward
quelle
Vielen Dank für die umfassende Antwort. Ich glaube jedoch, ich habe den wahren Fall für meine Probleme identifiziert. In meinem Beitrag auf mich aufmerksam gemacht. stackoverflow.com/questions/3645889/… . Danke dir.
James Moore
1
Das Ersetzen von / bin / bash durch cmd hat sich nicht wirklich gleich verhalten, da ich ein Programm erstellt habe, das dasselbe tat, aber Probleme mit bash hatte. In meinem Fall funktioniert das Öffnen von drei separaten Threads für jede Eingabe / Ausgabe / Fehler am besten problemlos für interaktive Befehle für lange Sitzungen.
Johnydep
@AlexMills: Bedeutet Ihr zweiter Kommentar, dass Sie Ihr Problem gelöst haben? Ihr erster Kommentar enthält bei weitem nicht genug Details, um zu sagen, wo das Problem liegt und warum Sie "plötzlich" Ausnahmen bekommen.
Luke Woodward
@ Luke, der Link, den ich direkt über Ihrem Kommentar bereitgestellt habe, löst mein Problem
Alexander Mills
4

Ich denke, Sie können Thread wie Dämonen-Thread zum Lesen Ihrer Eingabe verwenden und Ihr Ausgabeleser befindet sich bereits in der while-Schleife im Haupt-Thread, sodass Sie gleichzeitig lesen und schreiben können. Sie können Ihr Programm folgendermaßen ändern:

Thread T=new Thread(new Runnable() {

    @Override
    public void run() {
        while(true)
        {
            String input = scan.nextLine();
            input += "\n";
            try {
                writer.write(input);
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }
} );
T.start();

und Sie können Leser wird wie oben sein, dh

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

Machen Sie Ihren Autor als endgültig, sonst ist er für die innere Klasse nicht zugänglich.

Shashank Jain
quelle
1

Sie haben writer.close();in Ihrem Code. Bash erhält also EOF stdinund beendet das Programm. Dann bekommen Sie Broken pipebeim Versuch, aus der nicht mehr existierenden stdoutBash zu lesen .

gpeche
quelle