Processo Java con stream di input / output

Ho il seguente esempio di codice qui sotto. In questo modo è ansible inserire un comando per la shell bash, ad esempio il echo test e ottenere il risultato echo indietro. Tuttavia, dopo la prima lettura. Altri flussi di output non funzionano?

Perché questo o sto facendo qualcosa di sbagliato? Il mio objective finale è quello di creare un’attività programmata con thread che esegua periodicamente un comando su / bash in modo che OutputStream e InputStream debbano lavorare in tandem e non smettere di funzionare. Ho anche riscontrato l’errore java.io.IOException: Broken pipe qualche idea?

Grazie.

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

In primo luogo, consiglierei di sostituire la linea

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

con le linee

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

ProcessBuilder è nuovo in Java 5 e rende più semplice l’esecuzione di processi esterni. A mio parere, il suo miglioramento più significativo rispetto a Runtime.getRuntime().exec() è che consente di redirect l’errore standard del processo figlio nel suo output standard. Ciò significa che hai solo un InputStream da cui leggere. Prima di questo, era necessario avere due thread separati, uno in lettura da stdout e uno in lettura da stderr , per evitare il riempimento del buffer degli errori standard mentre il buffer di output standard era vuoto (causando il blocco del processo figlio) o viceversa.

Successivamente, i loop (di cui hai due)

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

esce solo quando il reader , che legge lo standard output del processo, restituisce la fine del file. Questo succede solo quando il processo di bash termina. Non restituirà la fine del file se al momento non si verifica più output dal processo. Invece, aspetterà la prossima riga di output dal processo e non tornerà finché non avrà questa riga successiva.

Poiché stai inviando due righe di input al processo prima di raggiungere questo ciclo, il primo di questi due loop si bloccherà se il processo non è terminato dopo queste due righe di input. Si siederà lì in attesa che venga letta un’altra linea, ma non ci sarà mai un’altra riga da leggere.

Ho compilato il tuo codice sorgente (sono su Windows al momento, quindi ho sostituito /bin/bash con cmd.exe , ma i principi dovrebbero essere gli stessi) e ho scoperto che:

  • dopo aver digitato due righe, viene visualizzato l’output dei primi due comandi, ma il programma si blocca,
  • se scrivo, ad esempio, echo test e quindi esco, il programma lo rende fuori dal primo ciclo da quando il processo cmd.exe è terminato. Il programma chiede quindi un’altra riga di input (che viene ignorata), salta direttamente sul secondo ciclo da quando il processo figlio è già uscito e quindi si chiude.
  • se scrivo exit e poi echo test , ottengo un IOException che lamenta la chiusura di un pipe. Ci si deve aspettare – la prima riga di input ha causato l’uscita del processo, e non c’è nessun posto dove inviare la seconda linea.

Ho visto un trucco che fa qualcosa di simile a quello che sembra, in un programma su cui ho lavorato. Questo programma manteneva un certo numero di shell, eseguiva comandi in esse e leggeva l’output da questi comandi. Il trucco usato era sempre scrivere una linea “magica” che segna la fine dell’output del comando della shell, e usarla per determinare quando l’output dal comando inviato alla shell era finito.

Ho preso il tuo codice e ho sostituito tutto dopo la riga che assegna allo writer con il seguente ciclo:

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

Dopo aver fatto ciò, ho potuto eseguire in modo affidabile alcuni comandi e avere l’output di ognuno di tornare da me individualmente.

I due comandi echo --EOF-- nella riga inviata alla shell sono lì per assicurare che l’output dal comando venga terminato con --EOF-- anche nel risultato di un errore dal comando.

Certo, questo approccio ha i suoi limiti. Queste limitazioni includono:

  • se inserisco un comando che aspetta l’input dell’utente (ad esempio un’altra shell), il programma sembra bloccarsi,
  • presume che ogni processo eseguito dalla shell termini l’output con una nuova riga,
  • diventa un po ‘confuso se il comando eseguito dalla shell capita di scrivere una riga --EOF-- .
  • bash riporta un errore di syntax ed esce se si inserisce del testo con un non corrispondente ) .

Questi punti potrebbero non essere importanti per te, qualunque cosa tu stia pensando di eseguire come compito programmato, sarà limitato a un comando oa un piccolo insieme di comandi che non si comporteranno mai in modo patologico.

EDIT : migliorare la gestione dell’uscita e altre piccole modifiche dopo aver eseguito questo su Linux.

Penso che tu possa usare thread come demon-thread per leggere i tuoi input e il tuo lettore di output sarà già in loop while nel thread principale in modo da poter leggere e scrivere contemporaneamente. Puoi modificare il tuo programma in questo modo:

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

e tu puoi leggere il lettore come sopra

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

rendere il tuo scrittore come finale altrimenti non sarà in grado di accedere per class interiore.

Hai writer.close(); nel tuo codice. Quindi bash riceve EOF sul suo stdin ed esce. Quindi ottieni Broken pipe quando provi a leggere dallo stdout della bash defunta.