Java - Prozess Output lesen

Impact

NGBler
Registriert
14 Juli 2013
Beiträge
155
Hey ihr ;)

Mein Plan ist es, mit Java einen Prozess (in Linux) zu starten und den kontinuierlichen Output des Prozesses dann zu verarbeiten, Zeile für Zeile.
Ansich hört es sich nicht schwierig an (ist es bestimmt auch nicht), jedoch schaffe ich es aktuell einfach nicht an den Output des Prozesses zu kommen. Ich habe mit Hilfe des ProzessBuilders den Prozess ausgeführt und dieser läuft. Der nächste Schritt für mich wäre es jetzt, den Output (der ja eigentlich kontinuierlich in unregelmäßigen Abständen auf der Konsole ausgegeben wird) zu verarbeiten und je nach Output irgendwas zu triggern.

Doch wie kann ich den kontinuierlichen Output lesen?

Ich habe bereits beim ProcessBuilder die Methode redirectOutput(new File(xyz)) ausgeführt, so dass ich sehen konnte, das der Prozess wirklich ausgeführt wird und Output erzeugt. Das klappt auch prima! Doch ich möchte nun nicht den Output in eine Datei schreiben, den Output dann Zeile für Zeile aus der Datei holen und dann darauf reagieren, sondern ich möchte es ohne diesen Zwischenschritt der Datei lösen. Das muss doch irgendwie machbar sein, oder?!

Ich freue mich über Tipps! :) :beer:
 
Was genau hast du vor? Eventuell kann man eine einfachere Lösung finden.
 
  • Thread Starter Thread Starter
  • #3
Naja, im Prinzip habe ich genau das vor, was ich beschrieben habe.

Es geht darum Whatsapp (mit yowsup) auf dem PI am Laufen zu haben und auf eingehende Nachrichten zu reagieren. Wie genau darauf reagiert wird bzw. was gemacht werden soll habe ich mir noch nicht überlegt, auf jeden Fall müssen dazu aber die eingehenden Nachrichten, welche aktuell auf der Konsole ausgegeben werden, irgendwie geparst werden.

yowsup selbst ist in Python geschrieben und theoretisch könnte man sicherlich auch da direkt ansetzen. Jedoch habe ich mit Python bislang wenig Erfahrung gemacht und wollte eigentlich auch unabhängig von yowsup bleiben.
Es kann ja nun eigentlich wirklich nicht so schwer sein vernünftig an den Output zu gelangen...

Theoretisch würde es ja sogar funktionieren den Output direkt über die Datei zu nehmen (mittels des redirect-Aufrufs am ProcessBuilder), doch im Endeffekt habe ich dann eine unnötige Datei, welche sogar im Laufe der Zeit größer und größer wird.
 
@Imp4c

Dann würde ich doch bei yowsup ansetzen und die Ausgabe an eine Datei anfügen, ist die Ausgabe größer als X Zeilen oder der letzten geparsten Nachricht, so leerst du diese wieder.
Du bräuchtest dann nur einen Kontrollmechanismus der sicherstellt das du die Daten die gerade geschrieben worden sind auch wirklich verarbeitet hast...

In Python kann man aber auch gut die Dateiattribute wie letzte Änderung und letzten Zugriff mittels " " ermitteln.

Funktioniert in Python 2.x wie auch in Python 3.

-----

Ich bin ja nicht wirklich ein Linux Guru, aber das Umleiten der Konsolenausgabe in eine Datei mittels "> MeineDatei.txt" würde nicht funktionieren?
Du mußt dann aber immer noch "sicherstellen" das die Datei dann irgendwann gelöscht wird, damit diese nicht ins unermeßliche anwächst....

Ich würde es aber als leichter ansehen und auch effektiver direkt in yowsup diese Test einzubauen bzw. dann auch in Java. Da hast du vermutlich etwas mehr Kontrolle wie und wann und wo die Dateien erstellt werden. Es hätte dann sogar den Vorteil das du yowsup vom weiteren Parsen (wenn nicht gethreaded) blocken könntest, bevor der input (aufgrund des letzten Zugriffs) nicht oder noch nicht vearbeitet wurde.

Es gibt in Python auch die Möglichkeit eine "tmpfile" erstellen zu lassen, das gelöscht wird so bald nichts mehr darauf verweist, wenn die Anwendung ordentlich geschlossen wird. ( )
 
Die ProcessBuilder-Dokumentation meint: Lass redirectOutput() weg (PIPE ist Standard) und nutz anschließend Process.getInputStream(). Über diesen Stream solltest du wie üblich die Ausgabe des Prozesses lesen können.
Einen InputStream zeilenweise einlesen kannst du bspw. über einen BufferedReader lösen (new BufferedReader(new InputStreamReader(...))), der müsste eine readLine()-Methode haben.
Falls die Ausgabe auf stderr (statt stdout) erfolgt, musst du analog Process.getErrorStream() nutzen.
 
  • Thread Starter Thread Starter
  • #6
Danke für die Beiträge bisher.

Ich denke ich konnte das Problem etwas eingrenzen, allerdings noch nicht lösen.

Ich habe nun die Ausgabe so umgebogen (mit redirectOutput(newFile(xyz)), so dass diese nicht auf der Console angezeigt wird, sondern in eine Datei geschrieben wird. Das funktioniert auch, allerdings steht die komplette Ausgabe erst nach Beendigung des Programms in der Datei - sie wächst also nicht live (wie es allerdings die Consolenausgabe des Prozesses tut - Zeile für Zeile). Wie kann das sein?

Kann es sein, dass alle bisher ausprobierten Reader (LineNumberReader, BufferedReader, dieses redirectOutput) auf ein bestimmtes Zeichen am Ende einer Zeile warten, bis diese irgendwas erkennen? Beim BufferedReader in der Doku von readLine() steht folgendes:
Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.

Das bedeutet ja, dass wenn die Line niemals mit \n oder \r beendet ist, die Methode immer null zurück gibt. Mh, komisch alles.

@theSplit:
Natürlich würde es dann irgendwann mehr Sinn machen das alles schon in Python in eine Datei zu schreiben und nur noch diese dann aus Java zu öffnen. Ich habe jedoch gedacht es geht eben auch "eleganter" und ohne den Umweg über die Datei. Ich hatte mir den Flow so vorgestellt:
yowsup(Python) -> Console -> Java parst Console Zeile für Zeile
und nicht
yowsup(Python) -> speichern in Datei -> Java -> öffnen der Datei und Zeile für Zeile parsen
 
Ich habe nun die Ausgabe so umgebogen (mit redirectOutput(newFile(xyz)), so dass diese nicht auf der Console angezeigt wird, sondern in eine Datei geschrieben wird. Das funktioniert auch, allerdings steht die komplette Ausgabe erst nach Beendigung des Programms in der Datei - sie wächst also nicht live (wie es allerdings die Consolenausgabe des Prozesses tut - Zeile für Zeile). Wie kann das sein?

Mal eine Vermutung in den Raum geworfen:
Kann es sein das die Ausgabe des Prozesses erst "final" (zum bearbeiten/speichern/weiterverarbeiten) wird nachdem der Prozess wirklich als "Beendet" gekennzeichnet ist?
Schließlich weiß kein Programm ab welchem Zeitpunkt die Ausgabe wirklich abgeschlossen worden ist. Ein Newline ist kein "\0" Terminator eines Strings und die Zeichenkette/Konsolen Ausgabe, gilt nicht als beendet. :unknown:

Vielleicht wäre es leichter den Python Code als separaten Prozess periodisch zu starten, beenden zu lassen und den Output dann zu verarbeiten? Ist natürlich bei einer zeitkritischen Anwendung (weil sollte ja Live passieren) kein wirklich schlaues vorgehen, aber vielleicht ginge es dann den Output mitzuschneiden da in sich abgeschlossen?

Ich hab auch mal selbst danach gesucht, habe dabei aber auch keine Antwort auf meiner Vermutung erhalten, jedoch einen mehr "unix like" way gefunden:
 
Dateien schreiben ist normalerweise gepuffert. Neuer Inhalt wird in der Datei erst dann periodisch sichtbar, wenn der Puffer vollläuft und in die Datei gespült wird. Wie groß der Puffer ist, hängt von der jeweiligen Implementierung ab.

Der saubere Ansatz ist allerdings das, was Rakorium-M sagt: ohne Datei über getInputStream().
 
Zuletzt bearbeitet:
@Brother John

Kannst du da etwas zu meiner "Theorie" sagen? - Mir ist es nicht ganz nachvollziehbar wann der Hauptprozess gemeldet bekommt wenn ein Kind Prozess wirklich alle Daten geschrieben/ausgegeben hat. Um beim Threading zu bleiben. Oder geht das Output -> Processing in einem Abwasch nachdem eine Zeile "abgeschlossen" wurde, so das der Hauptthread oder jede andere Anwendung ein "Daten wurden geschireben", weiter zur Verarbeitung, Signal bekommen kann?

Der Hauptprozess weiß doch nie wann der Subprozess aufgehört Daten zu schreiben, weil nicht beendet?
Oder wird auf eine Newline gewartet was als abgeschlossen gilt?
 
Zuletzt bearbeitet:
Ich würde mal tippen, dass die Dateiausgabe gebuffert wird, um nicht für jedes einzelne Byte die Festplatte anzuwerfen. Daten werden also erst geschrieben, wenn bspw. 4KB Text angekommen sind, oder eben der Stream geschlossen wird (bspw. weil das Programm sich beendet). Wenn da wirklich nur einzelne Nachrichten ankommen kann es natürlich eine Weile dauern, bis die 4KB (oder wie viel auch immer) voll sind...
 
  • Thread Starter Thread Starter
  • #11
Ich habe heute wenig Zeit und schreibe morgen nochmal ausführlicher etwas dazu mit Codebeispiel.

getInputStream() habe ich natürlich schon versucht zu nutzen - ohne Erfolg.

Aktuell löse ich es jetzt so:
Ich schreibe bei jeder eingehenden Nachricht den Empfänger und die dazugehörige Nachricht in eine Datei (nennen wir sie messages.msg). Diese Datei öffne ich dann in Java mit Hilfe des LineNumberReader() und gehe Zeile für Zeile durch die Datei. Wenn ich bei der letzen Zeile angekommen bin prüfe ich jede Sekunde, ob eine neue Zeile hinzugekommen ist und falls ja gehts von vorne los.
 
Und wie löst du das anwachsen der Datei auf X kb/MB ? - Wann weiß die Java Anwendung das neue Daten vorhanden sind?
Und wann fängt das Python Skript bei null an?

Würde mich interessieren wie du das gelöst hast.
 
@theSplit
Das Prinzip ist bei Dateien und Konsolenausgabe dasselbe, weil i.d.R. beides gepufferte Streams sind. D.h. der Elternprozess kann immer dann neue Ausgaben sehen, wenn der Kindprozess seinen Ausgabepuffer spült. Wann das passiert, ist abhängig von der Implementierung im Kind. Eine maximale Puffergröße gibts immer. Bei textorientierten Streams löst außerdem oft ein Zeilenumbruch die Spülung aus. Zwingend ist das aber nicht unbedingt. Es sei denn, der Programmierer des Kinds löst explizit ein Spülen aus.

Beispiel: Bei den C++-iostreams ist '\n' ein simpler Zeilenumbruch.
Code:
Expand Collapse Copy
std::cout << "Hello, world!\n";
spült vielleicht, oder auch nicht.
Code:
Expand Collapse Copy
std::cout << "Hello, world!" << std::endl;
dagegen ist ein Zeilenumbruch + garantierte Spülung.

So ähnlich ist das auch in Python:
Code:
Expand Collapse Copy
print("Hello, world!")
hat einen Zeilenumbruch am Ende, erzwingt aber keine Spülung. Das braucht ab Python 3
Code:
Expand Collapse Copy
print("Hello, world!", flush=True)
oder alternativ (auch Python-2-kompatibel)
Code:
Expand Collapse Copy
print("Hello, world!")
sys.stout.flush()

Du hast recht, dass der definitv endgültige Jetzt-kommt-nix-mehr-Zeitpunkt der Tod des Kindprozesses ist. Wenn der regulär beendet wurde – und sauber programmiert ist – wird er beim Sterben auch nochmal spülen, sodass sämtliche Ausgaben für den Elternprozess sichtbar werden. Wenn er dagegen hart terminiert, kann es durchaus sein, dass einige Ausgaben verloren gehen, weil die noch im Puffer hängen und nicht mehr rausgeschrieben werden.

@Imp4c
Was genau klappt denn bei getInputStream() nicht? Ich hab vor ein paar Tagen erst damit rumgetestet und problemlos Python stdout und sterr per getInputStream() auf die Java-Konsole gekippt. Wenn du erst in Java in eine Datei umleitest und das funktioniert, hast du jedenfalls keine Spülprobleme. Wenn der Pythonprozess die Daten zu spät oder in zu großen Happen liefert, würde sich das auf die umgeleitete Datei genauso auswirken.
 
  • Thread Starter Thread Starter
  • #14
Also, ich habe das aktuell so gelöst:

yowsup wird gestartet und bei eingehenden Nachrichten wird die Nachricht und der Empfänger sowohl auf der Konsole ausgegeben, als auch in eine Datei geschrieben.

[src=python]def onTextMessage(self,messageProtocolEntity):
text_file = open("messages_py.msg", "a")
text_file.write('from:'+messageProtocolEntity.getFrom(False)+'\n')
text_file.write('text:'+messageProtocolEntity.getBody()+'\n')
text_file.close()

print("from:%s" % (messageProtocolEntity.getFrom(False)))
print("text:%s" % (messageProtocolEntity.getBody()))[/src]

Die Ausgabe auf der Konsole sieht dann so aus:
from:4912345
text:hallo
from:4998765
text:hallo zurück

Das Java-Programm öffnet nun diese Datei und liest sie bis zum Ende ein. Am Ende angekommen wird eine Sekunde gewartet und anschließend geprüft, ob neue Zeilen zum parsen vorhanden sind. Dies löse ich wie folgt:
[src=java]
FileReader messageFileReader = null;
try {
messageFileReader = new FileReader("messages_py.msg");
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
LineNumberReader lnr = new LineNumberReader(messageFileReader);
while (p.isAlive()) {
try {
String line = lnr.readLine();
if (line == null) {
Thread.sleep(1000);
continue;
}
String[] from = line.split(":");
line = lnr.readLine();
String[] content = line.split(":");
// z.B System.out.println(content[1]);
}
[/src]

Darüber, dass die Datei zu groß wird, habe ich mir bislang noch keine Gedanken gemacht. Ich könnte mir aber vorstellen, dass ich die Zeile aus der Datei lösche sobald sie abgearbeitet wurde. Bei dem geringen Nachrichtenverkehr bislang müsste ich aber wohl einige Jahre warte bis die Datei ein paar MB groß ist :D

@Brother John
Tja, was genau da nicht klappt kann ich dir gar nicht sagen. Es hapert auf jeden Fall an der Stelle, an der der BufferedReader readLine() auf dem inputStream ausführen soll. readLine() gibt nämlich immer null zurück, egal ob was auf der Konsole ausgegeben wurde oder nicht.
Sobald ich das Programm allerdings mit STRG-C abschieße, werden (manchmal, aber nicht immer!) noch kurz ein paar Zeilen ausgegeben.
Evtl. hilft es ja wenn ich in python noch zusätzlich sys.stdout.flush() ausführe?:unknown:
 
Das klingt schon nach einem Spülproblem. Probiers mal mit flush() und schau, ob sich was ändert.
 
Statt der messages_py.msg würd ich versuchen eine zu verwenden. Dann gibt es das Problem mit anwachsenden Schnittstellen-Datei nicht und vielleicht ist auch das Problem mit dem Flush dann behoben.


 
Zurück
Oben