입력 / 출력 스트림이있는 Java 프로세스


90

아래에 다음 코드 예제가 있습니다. 그러면 bash 쉘에 명령을 입력 echo test하고 결과가 다시 표시되도록 할 수 있습니다. 그러나 처음 읽은 후에. 다른 출력 스트림이 작동하지 않습니까?

왜 이것이 또는 내가 뭔가 잘못하고 있습니까? 내 최종 목표는 그래서에 / bash는 정기적으로 명령을 실행하는 스레드 예약 된 작업을 생성하는 것입니다 OutputStreamInputStream연계하여 작업과 정지 작업을하지해야합니다. 또한 오류가 발생했습니다.java.io.IOException: Broken pipe 어떤 아이디어라도 있습니까?

감사.

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

"Broken pipe"는 아마도 자식 프로세스가 종료되었음을 의미합니다. 다른 문제가 무엇인지 확인하기 위해 나머지 코드를 완전히 살펴 보지 않았습니다.
vanza

1
별도의 스레드를 사용, 그것은 잘 작동합니다
Johnydep

답변:


139

먼저 라인 교체를 권장합니다.

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

라인

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

ProcessBuilder는 Java 5의 새로운 기능이며 외부 프로세스를 더 쉽게 실행할 수 있습니다. 제 생각에 가장 중요한 개선점 Runtime.getRuntime().exec()은 자식 프로세스의 표준 오류를 표준 출력으로 리디렉션 할 수 있다는 것입니다. 이것은 당신이 InputStream읽을 것이 하나만 있다는 것을 의미합니다 . 그 전에는 표준 출력 버퍼가 비어있는 동안 (하위 프로세스가 중단되는) 표준 오류 버퍼 채우기를 방지하기 위해 stdout에서 읽는 것과 에서 읽는 두 개의 별도 스레드가 필요했습니다 stderr.

다음으로 루프 (두 개가 있음)

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

reader프로세스의 표준 출력에서 ​​읽는이 파일의 끝을 반환 할 때만 종료됩니다 . 이것은 bash프로세스가 종료 될 때만 발생합니다 . 현재 프로세스에서 더 이상 출력이없는 경우 파일 끝을 반환하지 않습니다. 대신, 프로세스에서 출력의 다음 줄을 기다렸다가 다음 줄을 가질 때까지 반환하지 않습니다.

이 루프에 도달하기 전에 두 줄의 입력을 프로세스에 보내므로이 두 줄의 입력 후에 프로세스가 종료되지 않으면 두 루프 중 첫 번째 루프가 중단됩니다. 다른 줄을 읽을 때까지 기다리지 만 다른 줄은 읽을 수 없습니다.

나는 소스 코드를 (I 교체 그래서, 지금 윈도우에있어 컴파일 /bin/bashcmd.exe있지만 기본 원칙은 동일해야합니다), 그리고 그 발견 :

  • 두 줄을 입력하면 처음 두 명령의 출력이 표시되지만 프로그램이 중단됩니다.
  • 라고 입력 echo test한 다음을 입력 exit하면 cmd.exe프로세스가 종료 된 이후 프로그램이 첫 번째 루프에서 빠져 나옵니다 . 그런 다음 프로그램은 다른 입력 줄 (무시 됨)을 요청하고 자식 프로세스가 이미 종료되었으므로 두 번째 루프를 건너 뛰고 자체 종료합니다.
  • 입력 exit하고를 입력 echo test하면 파이프가 닫혀 있다는 IOException이 발생합니다. 이것은 예상 할 수 있습니다. 첫 번째 입력 라인은 프로세스를 종료 시켰고 두 번째 라인을 보낼 곳이 없습니다.

나는 내가 작업했던 프로그램에서 당신이 원하는 것과 비슷한 것을하는 트릭을 보았다. 이 프로그램은 여러 셸을 유지하고 그 안에서 명령을 실행하고이 명령의 출력을 읽습니다. 사용 된 트릭은 항상 셸 명령의 출력 끝을 표시하는 '마법'줄을 작성하고이를 사용하여 셸로 보낸 명령의 출력이 언제 완료되었는지 확인하는 것입니다.

나는 당신의 코드를 가져 와서 writer다음 루프로 할당하는 줄 뒤의 모든 것을 바꿨습니다 .

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

이렇게 한 후 몇 가지 명령을 안정적으로 실행하고 각 명령의 출력을 개별적으로 나에게 반환 할 수있었습니다.

echo --EOF--쉘에 전송 된 두 개의 명령은 명령 --EOF--의 오류 결과에서도 명령의 출력이 종료되도록하기 위해 있습니다.

물론이 접근 방식에는 한계가 있습니다. 이러한 제한 사항은 다음과 같습니다.

  • 사용자 입력 (예 : 다른 셸)을 기다리는 명령을 입력하면 프로그램이 중단 된 것처럼 보입니다.
  • 쉘에 의해 실행되는 각 프로세스가 출력을 개행으로 종료한다고 가정합니다.
  • 쉘에 의해 실행되는 명령이 행을 작성하면 약간 혼란스러워집니다 --EOF--.
  • bash구문 오류를보고하고 일치하지 않는 일부 텍스트를 입력하면 종료됩니다 ).

예약 된 작업으로 실행하려는 것이 무엇이든 이러한 병리학 적 방식으로 작동하지 않는 명령이나 작은 명령 집합으로 제한되는 경우 이러한 점은 중요하지 않을 수 있습니다.

편집 : Linux에서 실행 한 후 종료 처리 및 기타 사소한 변경 사항을 개선합니다.


포괄적 인 답변을 주셔서 감사합니다. 그러나 제 문제에 대한 진정한 사례를 파악한 것 같습니다. 귀하의 게시물에서 내 관심을 끌었습니다. stackoverflow.com/questions/3645889/... . 감사합니다.
James moore

1
/ bin / bash를 cmd로 바꾸는 것은 똑같지 만 bash에 문제가있는 프로그램을 만들었 기 때문에 실제로 동일하게 작동하지 않았습니다. mycase에서는 각 입력 / 출력 / 오류에 대해 세 개의 개별 스레드를 여는 것이 긴 세션 대화 형 명령에 문제없이 가장 잘 작동합니다.
Johnydep 2011


@AlexMills : 두 번째 의견은 문제를 해결했음을 의미합니까? 첫 번째 댓글에는 문제가 무엇인지, 왜 '갑자기'예외가 발생하는지에 대한 자세한 내용이 없습니다.
Luke Woodward 2013 년

@Luke은, 난 그냥 당신의 코멘트 위에 제공된 링크 내 문제를 해결
알렉산더 밀스에게

4

입력을 읽기 위해 demon-thread와 같은 스레드를 사용할 수 있으며 출력 리더는 이미 메인 스레드에서 while 루프에 있으므로 동시에 읽고 쓸 수 있습니다. 다음과 같이 프로그램을 수정할 수 있습니다.

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

그리고 당신은 위와 동일하게 읽을 수 있습니다.

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

작성자를 최종으로 만드십시오. 그렇지 않으면 내부 클래스에서 액세스 할 수 없습니다.


1

당신은이 writer.close();코드에서. 따라서 bash는 EOF를 수신 stdin하고 종료합니다. 그런 다음 없어진 bash Broken pipe에서 읽으려고 할 때 얻 습니다 stdout.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.