Em primeiro lugar, eu recomendaria substituir a linha
Process process = Runtime.getRuntime ().exec ("/bin/bash");
com as linhas
ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();
ProcessBuilder é novo no Java 5 e torna a execução de processos externos mais fácil. Em minha opinião, sua melhoria mais significativa Runtime.getRuntime().exec()é que ele permite redirecionar o erro padrão do processo filho para sua saída padrão. Isso significa que você só tem um InputStreampara ler. Antes disso, você precisava ter dois Threads separados, um lendo stdoute outro lendo stderr, para evitar o preenchimento do buffer de erro padrão enquanto o buffer de saída padrão estava vazio (fazendo com que o processo filho travasse) ou vice-versa.
Em seguida, os loops (dos quais você tem dois)
while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}
só sai quando o reader, que lê a saída padrão do processo, retorna o fim do arquivo. Isso só acontece quando o bashprocesso é encerrado. Ele não retornará o fim do arquivo se no momento não houver mais saída do processo. Em vez disso, ele aguardará a próxima linha de saída do processo e não retornará até que tenha a próxima linha.
Como você está enviando duas linhas de entrada para o processo antes de chegar a este loop, o primeiro desses dois loops irá travar se o processo não tiver saído após essas duas linhas de entrada. Ele ficará parado esperando que outra linha seja lida, mas nunca haverá outra linha para ser lida.
Compilei seu código-fonte (estou no Windows no momento, então substituí /bin/bashpor cmd.exe, mas os princípios devem ser os mesmos) e descobri que:
- depois de digitar duas linhas, a saída dos dois primeiros comandos aparece, mas então o programa trava,
- se eu digitar, digamos,
echo teste, em seguida exit, o programa sairá do primeiro loop desde que o cmd.exeprocesso foi encerrado. O programa então pede outra linha de entrada (que é ignorada), pula direto sobre o segundo loop, uma vez que o processo filho já saiu, e então sai sozinho.
- se eu digitar
exite echo test, em seguida , obtenho uma IOException reclamando sobre um tubo sendo fechado. Isso era esperado - a primeira linha de entrada fez com que o processo fosse encerrado e não há para onde enviar a segunda linha.
Eu vi um truque que faz algo semelhante ao que você parece querer, em um programa que eu costumava trabalhar. Este programa mantinha vários shells, executava comandos neles e lia a saída desses comandos. O truque usado era sempre escrever uma linha 'mágica' que marca o fim da saída do comando shell e usá-la para determinar quando a saída do comando enviado ao shell havia terminado.
Peguei seu código e substituí tudo após a linha que atribui a writerpelo seguinte loop:
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;
}
}
Depois de fazer isso, eu poderia executar alguns comandos de forma confiável e ter a saída de cada um voltando para mim individualmente.
Os dois echo --EOF--comandos na linha enviada para o shell estão lá para garantir que a saída do comando seja encerrada --EOF--mesmo em caso de erro do comando.
Claro, essa abordagem tem suas limitações. Essas limitações incluem:
- se eu inserir um comando que aguarda a entrada do usuário (por exemplo, outro shell), o programa parece travar,
- ele assume que cada processo executado pelo shell termina sua saída com uma nova linha,
- fica um pouco confuso se o comando executado pelo shell escrever uma linha
--EOF-- .
bashrelata um erro de sintaxe e sai se você inserir algum texto com um não correspondente ).
Esses pontos podem não importar para você se tudo o que você está pensando em executar como uma tarefa agendada for restrito a um comando ou um pequeno conjunto de comandos que nunca se comportarão de maneira tão patológica.
EDIT : melhore o tratamento de saída e outras pequenas alterações após a execução no Linux.