cmd über Java steuern

pokke

Grünschnabel
Hallo,

ich habe ein Programm mit dem ich dateien in ein Postgre DB schreiben möchte!
Das Problem dabei ist, dass ich eine Möglichkeit brauche um die psql.exe von Java aus starten zu können bzw. starten kann ich die exe, aber bei psql wird automatisch das pwd abgefragt und ich bräuchte ne Idee wie ich die Eingabe auf die Konsole schicken kann ohne das ich das pwd auf der Konsole nochmals eingeben muss. Habe auch schon Möglichkeiten über den OutputStream gelesen aber das Problem konnte ich trotzdem nicht lösen!
Außerdem kann ich den Inputstream nicht abfangen den mir das psql schickt mit: "Bitte geben sie das Passwort ein:"

Muss ich die Threads ansteuern oder gibt es direkte Möglichkeiten mein Java Programm mit der Konsole direkt kommunizieren zu lassen?
Hat jmd. einen Vorschlag?

Quelltext:

Process p = Runtime.getRuntime().exec("c:\\programme\\postgresql\\8.1\\bin\\psql.exe -f c:\\test\\sql.txt -d datenbank -h host -p port -U user");
BufferedReader test = new BufferedReader(new InputStreamReader(p.getInputStream()));
// hier bleibt dann das Programm stehen
String a = test.readLine();
System.out.println(a);
 

Thomas Darimont

Erfahrenes Mitglied
Hallo,

schau mal hier:
Java:
/**
 * 
 */
package de.tutorials;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Scanner;

/**
 * @author Thomas.Darimont
 */
public class JavaCommandLineWrapperExample {

  /**
   * @param args
   */
  public static void main(String[] args) throws Exception {
    ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe").redirectErrorStream(true);
    Process process = processBuilder.start();
    createAndStartProcessOutputHandlerThread(process);
    Thread processInputHandlerThread = createAndStartProcessInputHandlerThread(process);
    process.waitFor();
    processInputHandlerThread.interrupt();
  }


  private static Thread createAndStartProcessInputHandlerThread(final Process process) {
    Thread thread = new Thread(new Runnable() {
      public void run() {
        PrintWriter printWriter = new PrintWriter(process.getOutputStream());
        Scanner scanner = new Scanner(System.in);
        try {
          while (scanner.hasNextLine()) {
            printWriter.println(scanner.nextLine());
            printWriter.flush();
            Thread.sleep(50);
          }
        } catch (InterruptedException interruptedException) {
          // ignore Process shutdown
        }
        scanner.close();
      }
    });
    thread.start();
    return thread;
  }


  private static Thread createAndStartProcessOutputHandlerThread(final Process process) {
    Thread thread = new Thread(new Runnable() {
      public void run() {
        try {
          char c = Character.MAX_VALUE;
          while ((c = (char) process.getInputStream().read()) != Character.MAX_VALUE) {
            System.out.print(c);
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    });
    thread.start();
    return thread;
  }

}

Gruß Tom
 

pcworld

Grünschnabel
Die Klasse "JavaCommandLineWrapperExample" ist sehr gut zu gebrauchen. Danke!

Jetzt habe ich allerdings noch das Problem, dass ich den Output in eine JTextArea weiterleiten will.
Das wäre nicht das größte Problem, denn es soll auch noch die Möglichkeit bestehen, dass der User Text in die JTextArea eingeben kann und nach Drücken von Enter diese Eingabe an die CMD weitergeleitet wird.
Also ein Ersatz für das Standard-CMD-Fenster, wenn man unter Start -> Ausführen -> "cmd.exe" eingibt.

Um einen Output-Stream in eine JTextArea zu leiten, habe ich folgende Klasse gefunden:
Code:
import java.io.OutputStream;

import javax.swing.JTextArea;

public class TextAreaOutputStream extends OutputStream {
	JTextArea ta;

	public TextAreaOutputStream(JTextArea t) {
		super();
		this.ta = t;
	}

	public void write(int i) {
		char[] chars = new char[1];
		chars[0] = (char) i;
		String s = new String(chars);
		ta.append(s);
	}

	public void write(char[] buf, int off, int len) {

		String s = new String(buf, off, len);
		ta.append(s);
	}
}

Allerdings weiß ich schlecht, wie das mit der Usereingabe gehen könnte - wär nett, wenn ihr mir da helfen könntet! :)

Gruß,
pcworld
 

pcworld

Grünschnabel
Ich habe die Methode createAndStartProcessOutputHandlerThread() der Klasse JavaCommandLineWrapperExample mal so verändert, dass sie mit dem oben geposteten TextAreaOutputStream klar kommt, d. h., Output funktioniert. Nur, das schwierigste, der User-Input, fehlt noch...

Code:
public class JavaCommandLineWrapperExample {

	private static TextAreaOutputStream taos;
 // ....
	private static Thread createAndStartProcessOutputHandlerThread(
			final Process process) {
		Thread thread = new Thread(new Runnable() {
			public void run() {
				try {
					char c = Character.MAX_VALUE;
					while ((c = (char) process.getInputStream().read()) != Character.MAX_VALUE) {
						System.out.print(c);
						taos.write(c);
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
		thread.start();
		return thread;
	}
}
 

Thomas Darimont

Erfahrenes Mitglied
Hallo,

suchst du sowas?
Java:
package de.tutorials.training;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.concurrent.Executors;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class JavaCommandLineWrapperExample extends JFrame {

  JTextArea txtConsole;
  JTextField txtCommand;

  public JavaCommandLineWrapperExample() {
    super("JavaCommandLineWrapperExample");
    setDefaultCloseOperation(EXIT_ON_CLOSE);

    txtConsole = new JTextArea(20, 80);
    txtCommand = new JTextField(80);

    txtConsole.setEditable(false);

    JScrollPane scrollPane = new JScrollPane(txtConsole);

    add(scrollPane);
    add(txtCommand, BorderLayout.SOUTH);

    pack();
    setVisible(true);

    startShell();
  }


  void startShell() {
    ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe").redirectErrorStream(true);

    try {
      final Process process = processBuilder.start();
      final PrintWriter processInput = new PrintWriter(process.getOutputStream());
      captureProcessOutput(process);
      txtCommand.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          processInput.println(txtCommand.getText());
          processInput.flush();
          txtCommand.setText("");
        }
      });

      int exitCode = process.waitFor();
      System.exit(exitCode);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }


  void captureProcessOutput(final Process process) {
    Executors.newSingleThreadExecutor().execute(new Runnable() {
      @Override
      public void run() {
        try {
          char c = (char)-1;
          InputStream processOutput = process.getInputStream();
          while ((c = (char) processOutput.read()) != -1) {
            txtConsole.append(String.valueOf(c));
            txtConsole.setCaretPosition(txtConsole.getText().length());
          }  
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    });
  }


  public static void main(String[] args) throws Exception {
    new JavaCommandLineWrapperExample();

  }
}

Gruß Tom
 

pcworld

Grünschnabel
Vielen Dank!

Ich habe die Klasse um folgende Dinge erweitert:
  • Der Focus liegt standardmäßig auf dem JTextField.
  • Ist der Focus auf der JTextArea, und wird dann eine Taste gedrückt, wird das KeyEvent an das JTextField weitergeleitet.
  • Aussehen mehr nach der cmd.exe
  • Wenn von der cmd gesendet wird, dass der Bildschirminhalt gelöscht werden soll (z. B. über den Befehl "cls"), wird der Inhalt der JTextArea dementsprechend auch geleert. (Bitte testen, bin mir nicht sicher, ob das auf allen Windows-Systemen funktioniert!)

Code:
Code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.concurrent.Executors;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class JCLGUI extends JFrame {

	JTextArea txtConsole;
	JTextField txtCommand;

	public JCLGUI() {
		super("JavaCommandLineWrapperExample");
		setDefaultCloseOperation(EXIT_ON_CLOSE);

		txtConsole = new JTextArea(20, 80);
		txtConsole.setBackground(Color.BLACK);
		txtConsole.setForeground(Color.WHITE);
		txtConsole.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
		txtConsole.addKeyListener(new KeyAdapter() {
			@Override
			public void keyTyped(KeyEvent e) {
				txtCommand.requestFocus();
				txtCommand.dispatchEvent(e);
			}
		});
		txtCommand = new JTextField(80);
		txtCommand.setBackground(Color.BLACK);
		txtCommand.setForeground(Color.WHITE);
		txtCommand.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));

		txtConsole.setEditable(false);

		JScrollPane scrollPane = new JScrollPane(txtConsole);

		add(scrollPane);
		add(txtCommand, BorderLayout.SOUTH);

		setSize(900, 400);
		setVisible(true);
		txtCommand.requestFocus();

		startShell();
	}

	void startShell() {
		ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe")
				.redirectErrorStream(true);

		try {
			final Process process = processBuilder.start();
			final PrintWriter processInput = new PrintWriter(process
					.getOutputStream());
			captureProcessOutput(process);
			txtCommand.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					processInput.println(txtCommand.getText());
					processInput.flush();
					txtCommand.setText("");
				}
			});

			int exitCode = process.waitFor();
			System.exit(exitCode);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	void captureProcessOutput(final Process process) {
		Executors.newSingleThreadExecutor().execute(new Runnable() {
			@Override
			public void run() {
				try {
					char c = (char) -1;
					InputStream processOutput = process.getInputStream();
					while ((c = (char) processOutput.read()) != -1) {
						if ((byte) c == 12) {
							/*
							 * der Byte-Code, der gesendet wird, wenn der
							 * Bildschirm gelöscht werden soll, in cmd.exe der
							 * Befehl "cls"
							 */
							txtConsole.setText("");
						} else {
							// String s = Character.toString(c);
							// txtConsole.append(new
							// String(s.getBytes("CP1225")));
							txtConsole.append(String.valueOf(c));
						}
						txtConsole.setCaretPosition(txtConsole.getText()
								.length());
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
	}

	public static void main(String[] args) throws Exception {
		new JCLGUI();

	}
}

Allerdings habe ich noch ein Problem, nämlich mit der Schrott Kodierung von der cmd.exe.
Einfaches Beispiel: "dir" eingeben.
Output sollte sein: "Datenträger in Laufwerk C: ist Windows" - Das "ä" wird allerdings nicht richtig dargestellt.
Mit den Encodings habe ich schon rumgespielt, bin aber nicht zum Erfolg gekommen.
Wenn ich allerdings erst mit "CHCP 1252" die Kodierung umstelle und dann erst "dir" eingebe, werden die Umlaute richtig dargestellt. Ein Weg wäre jetzt, diese Eingabe einfach zu simulieren, allerdings ist das auch kein so guter Weg.
Weiß jemand, wie man das hinbekommen könnte?

Gruß,
pcworld
 

Anhänge

  • javacmdln.jpg
    javacmdln.jpg
    28,9 KB · Aufrufe: 390

GUEST53U6

Grünschnabel
Hallo

das ist wirklich ein schönes kleines Programm !

leider funktioniert es nicht ganz genau wie die cmd ...
ich habe es mir runtergeladen , weil ich damit sowas wie eine Remote Shell bauen wollte.

z.B bei MySQL gibt es folgendes Problem das ich bisher nicht gelöst habe, und was mich wirklich wundert warum es nicht funktioniert ....

ich gebe folgendes ein: mysql -u root -p testDB
dann passiert gar nichts mehr , Programm reagiert auf keine weiteren Eingaben,
obwohl man hier den Passwort Prompt von MySQL erwartet hätte

gebe ich die Anmeldeparameter so ein: mysql -uroot -pmeinpasswort testDB
dann passiert auch erstmal nichts, auch nicht der mysql prompt ist sichtbar, aber mysql wurde
als Prozess mit der angebenen Datenbank gestartet.
Dies jetzt mal ignorierend gebe ich jetzt ein paar SQL Befehle ein,
z.B. select * from account; (nichts passiert auf der Shell)
dann gebe ich ein exit;
daraufhin bekomme ich eine MySQL error Meldung , und ! aber auch auf einmal die Ausgabe vom
select * from account Befehl.

Hat jemand darauf eine Erklärung ?
Ich dachte vielleicht muss man einen separaten Ein-/Ausgabe Stream für den gestarteten MySQL Prozess anlegen , sozusagen für den Kind Prozess, aber dagegen spricht, dass die Ausgabe für den select Befehl
schließlich produziert wurde, wenn auch auf seltsame Weise an der falschen Stelle

mysql leitet seinen Ausgabestrom in den Eingabestrom der cmd ... liegt hier etwa das Problem ?

Gruß, Guest
 
Zuletzt bearbeitet:

sandaime

Grünschnabel
Hallo ich habe so ein ähnliches problem konnte aber den Code
Thomas Darimont nicht ganz in meins anpassen zumal ich den nicht richtig verstanden habe jedoch poste ich mll meinen Code in Hoffnung das ihr mit hilft oder auf die Sprünge helft.

Bei meinem Code passiert nur folgendes. In echtzeit wird die Rechnung in der Konsole in der Eclipse Konsole ausgegeben. Und erst wenn die Berechnung komplett fertig ist kommt die Konsolen Ausgabe in meine TextArea und in der zwischen erfriert die GUI, da die Schleife läuft.

Es geht um eine Bachelorarbeit und daher wäre ich froh über jede hilfe.


Code:
    private void konsoleEingabe(boolean neustart) {

        String austalPfad = cf.pathToAustalExe;
        String austalDirectory = curProjektPfad.split("austal2000.txt")[0];


        if (curProjektPfad == null) {
            JOptionPane.showMessageDialog(this, "Es ist kein Projekt ausgewählt oder erstellt worden!");
        } else {
            // TODO Thread einbauen
            // ProcessBuilder auf thread umstellen und asynchron in das TextFeld ergebnisAusgabe schrieben mittels .append
            // Siehe JavaCommandLineWrapperExample Zeile 80ff.
            ProcessBuilder builder = null;
            if( neustart ){
                builder = new ProcessBuilder(austalPfad,"-D", austalDirectory);
            } else {
                builder = new ProcessBuilder(austalPfad, austalDirectory);
            }
          
            builder.redirectErrorStream(true);
            Process p = null;
            try {
                p = builder.start();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            while (true) {
                try {
                    line = r.readLine();
                   
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                if (line == null) {
                    break;
                }
                ergebnisAusgabe.append(line+"\r\n");
                System.out.println(line);
            }
        }
    }
 

Cymatoxa

Mitglied
Hey sandaime,

zeitintensive Berechnungen im UI Thread führen dazu, dass wie in diesem Fall die UI nicht mehr oder langsam reagiert. Führe stattdessen die Berechnung in einem eigenen Thread aus. Nach jedem Berechnungsschritt kannst du das Zwischenergebnis in den Output schreiben. Benutze nur für die Ausgabe dann SwingUtilities.invokeLater(...), damit dies im UI Thread geschieht.
Es wäre evtl. gut zu wissen, was denn dein ergebnisAusgabe für ein Objekt ist. Wird das eventuell gebuffert?
Viele Grüße