PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [Java] Interagieren mit Command-line Programm


Markus89
2010-06-28, 19:09:00
Hallo,

ich möchte in Java mit einem Command-line Tool interagieren. Wenn ich nur den Outputstream von diesem Tool lesen möchte funktioniert das auch wunderbar. Jetzt kann es aber passieren, dass dieses Programm nicht sofort seinen Output generiert sondern erst Login+Passwort abfragt. So sieht das etwa aus:

Blablabla
Blablabla
<Login>: //Hier Login eingeben
<Password>: //Hier PW eingeben
Output

Hier mein derzeitiger Code:

Process process = Runtime.getRuntime().exec("login.exe");
InputStream stdout = process.getInputStream();
Scanner scanner = new Scanner(stdout);
scanner.useDelimiter("\\r\\n|\\r|\\n|:"); //scanner.nextLine() geht nicht, da Login+PW in der gleichen Zeile wie <Login/PW>: eingegeben werden muss
while (scanner.hasNext()) {
line = scanner.next();
System.out.println(line);
if (line.endsWith("<Login>"))
process.getOutputStream().write(login.getBytes());
if (line.endsWith("<Password>"))
process.getOutputStream().write(password.getBytes());
process.getOutputStream().flush();
}

Das funktioniert mit meinem eigenen C-Testprogramm auch wunderbar, aber auch nur, wenn ich vor jedem scanf() den Output flushe.

Ich habe aber leider nicht die Möglichkeit das reale Command-line Tool zu verändern. In diesem Fall passiert nämlich garnichts - scanner.hasNext() wartet endlos auf nicht kommenden Input.

Ich habe schon ein paar andere Möglichkeiten getestet, aber ohne Erfolg. Weiß jemand, wie man das "richtig" machen kann?

Markus

Baalzamon
2010-06-28, 20:14:25
Hmmm... ich weiss das ist nur die halbe Wahrheit, aber ich hatte mal ähnliche Problem unter der Unix Shell. Damals habe ich ein Tool namens Expect benutzt und, wie ich mir dachte, gibt es natürlich auch ein ExpectJ (http://expectj.sourceforge.net/).

Das ist genau für solche Sachen gemacht, vielleicht hilft es dir ja weiter.

Markus89
2010-06-28, 20:57:02
Danke für den Hinweis! Es ist total komisch, ich habe das eben mit ExceptJ versucht und es hat genau das gleiche Ergebnis gehabt wie mein Code oben. (mit flush() gings, ohne nicht). Dann hab ich das zwei drei mal gestartet und auf einmal hat es auch ohne flush() wunderbar funktioniert. Dann hab ich das Ganze mit meinem Code getestet und siehe da - es geht auch!

Seitdem hat es jedesmal wieder geklappt (auf beide Arten) - vorhin ging es bei keinem meiner ~50 Versuche...

Woran kann das liegen?

Geht doch nicht, das Problem besteht also immer noch. Es funktionert nur, wenn ich ganz am Anfang sofort Login+Passwort in den Stream schreibe. Das will ich aber natürlich nicht, sondern nur dann, wenn auch der Login abgefragt wird - und das ist eben nicht immer der Fall.

Ich habe so das Gefühl, dass es einfach an der C Library liegt, die den Output erst zwischenspeichert (wenn man ihn nicht mit flush() ausgibt). Da kann man von Java aus nichts dran machen...

Gast
2010-06-30, 22:22:38
Ich hab das jetzt nicht gegengeprüft, aber vll liegt es an der Scanner-Klasse, die wartet, dass eine Zeile vollständig ist.
Eventuell mal den "puren" InputStream benutzen.


Der Fehler muss ja an sich auch im Java-Programm liegen, denn: Auf der Kommandozeile siehst du ja den Text, also muss das Java-Programm den Text auch sehen. An fehlenden flushes dürfte es demnach nicht liegen.
Ich würde auch mal in der API-Doc suchen, ob da irgendein Puffer im weg ist und ob man den abschalten oder vll auch flushen kann.

randy
2010-07-01, 12:10:45
In diesem Fall passiert nämlich garnichts - scanner.hasNext() wartet endlos auf nicht kommenden Input.

Wenn ich das richtig verstanden habe, ist dein Problem, dass er gar nicht erst in die Schlaufe kommt? Ist evtl. das Pattern unvollständig und es wird etwas erwartet, was nicht kommt ?

Markus89
2010-07-01, 17:39:03
@Gast: Nein, daran kann es nicht liegen. Erstens gibt es scanner.nextLine() für die nächste Zeile und zweitens habe ich das auch mit den BufferedReader/InputStreamReader getestet - mit dem gleichen Ergebnis.

@randy: Genau, beim ersten Aufruf von scanner.hasNext() bleibt das Programm hängen. Es kann aber auch nicht am Pattern liegen, da ich es wie gesagt auch auf andere Wege probiert habe und es nur Zeilenumbrüche bzw. einen Doppelpunkt als Zeichen gibt.

Wenn ich im C-Programm immer flush() Aufrufe funktioniert es ja, deswegen ist es ja so verwirrend. Eigentlich sollte der Outputbuffer vom C-Programm geleert werden wenn man per scanf() auf eine Eingabe wartet - in der Kommandozeile geht es ja auch. Irgendwie scheint das Zusammenspiel C <-> Java nicht gut zu funktionieren.

Das einzige Szenario in dem das Ganze funktioniert ist (abgesehen von den Fall, dass keine Eingabe gefordert wird), wenn bevor scanner.hasNext() aufgerufen wird schon Login+PW in den OutputStream von Java geschrieben wird. Das ist aber nicht erwünscht, denn diese Eingabe wird nur in manchen Fällen - eben abhängig von der vorigen Ausgabe des Command-Line Tools - gebraucht. Versuche ich aber vom InputStream in Java zu lesen während das C-Programm auf die Logineingabe wartet, bleibt scanner.hasNext() hängen.

Pinoccio
2010-07-01, 18:23:02
Welches BS?
Ich hatte Schwierigkeiten mit ähnlichen Problemen, weil ich unter Win programmiert und getestet hatte und es unter Linux nicht lief. :-/

Ein Möglicher Test-Ansatz wäre, ein eigenes kleines JAVA-Program zu schreiben, daß als als Dummy dein C-Verhalten nachstellt. Dann kannst du da reingucken, was ankommt.

mfg

Markus89
2010-07-01, 19:48:00
BS ist Windows 7 und Windows XP, geht bei beiden nicht.

Ich habe eben auch mal eine Delphi-Konsolenanwendung getestet, und mit der funktioniert es ohne flush() wunderbar.

Funky Bob
2010-07-01, 20:22:31
Evtl. nochmal per Hand nen "\n" in den Stream schreiben, dann sollte das BS automatisch flushen (Ja, das steht nen println, aber man weiß ja nie ^^)

randy
2010-07-01, 20:58:37
was passiert wenn du anstatt

process.getOutputStream().write(login.getBytes());

einen PrintStream nimmst? Also:


[...]
PrintStream out = new PrintStream(process.getOutputStream());
[...]
out.println(login);
[...]
out.println(password);
[...]
out.flush();

Markus89
2010-07-01, 21:08:45
@randy: Wie ich im ersten Post geschrieben habe, habe ich keine möglichkeit das externe Programm zu verändern - mit einem flush() geht es ja auch, aber das bringt mir nichts.

@Funky Bob: Was soll das bringen? Ich komme ja nichtmal bis an die Stelle an der ich schreiben könnte:

Process process = Runtime.getRuntime().exec("login.exe");
InputStream stdout = process.getInputStream();
Scanner scanner = new Scanner(stdout);
scanner.useDelimiter("\\r\\n|\\r|\\n|:");
while (scanner.hasNext()) { // hier bleibt das Programm beim ersten Durchlauf hängen
line = scanner.next();
System.out.println(line);
if (line.endsWith("<Login>"))
process.getOutputStream().write(login.getBytes());
if (line.endsWith("<Password>"))
process.getOutputStream().write(password.getBytes());
process.getOutputStream().flush();
}

randy
2010-07-01, 22:44:48
Hi Markus vielleicht liegt ein Missverständnis vor, aber für meinen Vorschlag muss man doch die login.exe gar nicht ändern ?

edit:

also so:


Process process = Runtime.getRuntime().exec("login.exe");
InputStream stdout = process.getInputStream();
PrintStream out = new PrintStream(process.getOutputStream());
Scanner scanner = new Scanner(stdout);
scanner.useDelimiter("\\r\\n|\\r|\\n|:"); //scanner.nextLine() geht nicht, da Login+PW in der gleichen Zeile wie <Login/PW>: eingegeben werden muss
while (scanner.hasNext()) {
line = scanner.next();
System.out.println(line);
if (line.endsWith("<Login>"))
out.println(login);
process.getOutputStream().write(login.getBytes());
if (line.endsWith("<Password>"))
out.println(password);
process.getOutputStream().write(password.getBytes());
process.getOutputStream().flush();
out.flush();
}


wobei das bringt ja auch nix wenn du gar nicht in die schleife kommst