ti-enxame.com

Java Processo com fluxo de entrada / saída

Eu tenho o seguinte exemplo de código abaixo. Onde você pode inserir um comando para o bash Shell, ou seja, echo test e ter o resultado retornado. No entanto, após a primeira leitura. Outros fluxos de saída não funcionam?

Por que isso ou estou fazendo algo errado? Meu objetivo final é criar uma tarefa agendada Threaded que execute um comando periodicamente para/bash para que o OutputStream e o InputStream tenham que funcionar em conjunto e não parem de funcionar. Eu também tenho experimentado o erro Java.io.IOException: Broken pipe alguma idéia?

Obrigado.

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);
}
87
James moore

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

O ProcessBuilder é novo em Java 5 e facilita a execução de processos externos. Na minha opinião, sua melhoria mais significativa sobre 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, era necessário ter dois Threads separados, um lendo de stdout e um lendo de 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 fosse interrompido) 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 processo bash sai. 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 atingir esse loop, o primeiro desses dois loops será interrompido se o processo não tiver saído após essas duas linhas de entrada. Ele ficará lá aguardando que outra linha seja lida, mas nunca haverá outra linha para ser lida.

Eu 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 o programa trava,
  • se eu digitar, digamos, echo test e exit, o programa sairá do primeiro loop desde que o processo cmd.exe foi encerrado. O programa, em seguida, solicita outra linha de entrada (que é ignorada), ignora o segundo loop desde o processo filho já saiu e, em seguida, sai.
  • se eu digitar exit e, em seguida, echo test, recebo uma IOException reclamando sobre o fechamento de um pipe. Isso é 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 em torno de um número de shells, executava comandos neles e lia a saída desses comandos. O truque usado era sempre escrever uma linha 'mágica' que marca o final da saída do comando Shell e usá-la para determinar quando a saída do comando enviado para o Shell tinha terminado.

Eu peguei seu código e substituí tudo depois da linha que atribui a writer com o 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 fazer com que a saída de cada um voltasse para mim individualmente.

Os dois comandos echo --EOF-- na linha enviada para o Shell estão lá para garantir que a saída do comando seja finalizada com --EOF-- mesmo no resultado de um erro do comando.

Claro, essa abordagem tem suas limitações. Essas limitações incluem:

  • se eu digitar um comando que aguarde a entrada do usuário (por exemplo, outro Shell), o programa parece travar,
  • assume que cada processo executado pelo Shell termina sua saída com uma nova linha,
  • fica um pouco confuso se o comando que está sendo executado pelo Shell for escrever uma linha --EOF--.
  • bash informa 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 o que você está pensando em executar como uma tarefa agendada será restrito a um comando ou a um pequeno conjunto de comandos que nunca se comportarão de maneira patológica.

EDIT: melhora o tratamento de saída e outras pequenas mudanças após executar isto no Linux.

131
Luke Woodward

Eu acho que você pode usar thread como demon-thread para ler sua entrada e seu leitor de saída já estará em while loop no thread principal para que você possa ler e escrever ao mesmo tempo.Você pode modificar seu programa como este:

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 você pode leitor será o mesmo que acima, ou seja,.

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

faça seu escritor como final, caso contrário ele não será acessível pela classe interna.

4
Shashank Jain

Você tem writer.close(); no seu código. Então o bash recebe EOF no seu stdin e sai. Então você obtém Broken pipe ao tentar ler o stdout do bash extinto.

1
gpeche