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 InputStream
para ler. Antes disso, você precisava ter dois Threads separados, um lendo stdout
e 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 bash
processo é 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/bash
por 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 test
e, em seguida exit
, o programa sairá do primeiro loop desde que o cmd.exe
processo 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
exit
e 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 writer
pelo 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--
.
bash
relata 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.