Quy trình Java với Dòng đầu vào / đầu ra


90

Tôi có ví dụ mã sau đây bên dưới. Theo đó, bạn có thể nhập một lệnh vào bash shell tức là echo testvà có kết quả dội lại. Tuy nhiên, sau lần đọc đầu tiên. Các luồng đầu ra khác không hoạt động?

Tại sao lại như vậy hay tôi đang làm gì đó sai? Mục tiêu cuối cùng của tôi là tạo một tác vụ được lên lịch theo luồng thực thi một lệnh định kỳ tới / bash để các lệnh này OutputStreamInputStreamsẽ phải hoạt động song song và không ngừng hoạt động. Tôi cũng đã gặp lỗi java.io.IOException: Broken pipebất kỳ ý tưởng?

Cảm ơn.

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

"Đường ống bị hỏng" có thể có nghĩa là tiến trình con đã thoát. Chưa xem xét đầy đủ phần còn lại của mã của bạn để xem các vấn đề khác là gì.
vanza

1
sử dụng chủ đề riêng biệt, nó sẽ chỉ làm việc tốt
Johnydep

Câu trả lời:


139

Đầu tiên, tôi khuyên bạn nên thay thế dòng

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

với những dòng

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

ProcessBuilder là tính năng mới trong Java 5 và giúp chạy các quy trình bên ngoài dễ dàng hơn. Theo tôi, cải tiến đáng kể nhất của nó Runtime.getRuntime().exec()là nó cho phép bạn chuyển hướng lỗi tiêu chuẩn của quy trình con thành đầu ra tiêu chuẩn của nó. Điều này có nghĩa là bạn chỉ có một InputStreamđể đọc. Trước đó, bạn cần có hai Luồng riêng biệt, một đọc từ stdoutvà một đọc từ stderr, để tránh lấp đầy bộ đệm lỗi tiêu chuẩn trong khi bộ đệm đầu ra tiêu chuẩn trống (gây treo tiến trình con) hoặc ngược lại.

Tiếp theo, các vòng lặp (trong đó bạn có hai vòng)

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

chỉ thoát khi reader, được đọc từ đầu ra tiêu chuẩn của quy trình, trả về phần cuối của tệp. Điều này chỉ xảy ra khi bashquá trình thoát. Nó sẽ không trả về end-of-file nếu hiện tại không có thêm đầu ra từ quy trình. Thay vào đó, nó sẽ đợi dòng đầu ra tiếp theo từ tiến trình và không quay lại cho đến khi nó có dòng tiếp theo này.

Vì bạn đang gửi hai dòng đầu vào cho quy trình trước khi đến vòng lặp này, nên vòng đầu tiên trong hai vòng này sẽ bị treo nếu quy trình chưa thoát sau hai dòng đầu vào này. Nó sẽ ngồi đó chờ đọc dòng khác, nhưng sẽ không bao giờ có dòng khác để đọc.

Tôi đã biên dịch mã nguồn của bạn (hiện tại tôi đang sử dụng Windows, vì vậy tôi đã thay thế /bin/bashbằng cmd.exe, nhưng các nguyên tắc sẽ giống nhau) và tôi thấy rằng:

  • sau khi nhập hai dòng, đầu ra từ hai lệnh đầu tiên xuất hiện, nhưng sau đó chương trình bị treo,
  • nếu tôi nhập, giả sử, echo testvà sau đó exit, chương trình sẽ thoát khỏi vòng lặp đầu tiên kể từ khi cmd.exequá trình thoát ra. Sau đó, chương trình yêu cầu một dòng đầu vào khác (bị bỏ qua), bỏ qua thẳng vòng lặp thứ hai vì tiến trình con đã thoát, rồi tự thoát.
  • nếu tôi nhập exitvà sau đó echo test, tôi nhận được IOException phàn nàn về việc một đường ống bị đóng. Điều này được mong đợi - dòng đầu vào đầu tiên khiến quá trình thoát ra và không có nơi nào để gửi dòng thứ hai.

Tôi đã thấy một thủ thuật thực hiện điều gì đó tương tự như những gì bạn có vẻ muốn, trong một chương trình mà tôi đã từng làm việc. Chương trình này lưu giữ xung quanh một số shell, chạy các lệnh trong đó và đọc kết quả đầu ra từ các lệnh này. Thủ thuật được sử dụng là luôn viết ra một dòng 'ma thuật' đánh dấu sự kết thúc của đầu ra của lệnh shell và sử dụng nó để xác định thời điểm kết thúc đầu ra từ lệnh được gửi đến shell.

Tôi đã lấy mã của bạn và tôi đã thay thế mọi thứ sau dòng gán cho writerbằng vòng lặp sau:

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

Sau khi làm điều này, tôi có thể chạy một vài lệnh một cách đáng tin cậy và có kết quả đầu ra từ mỗi lệnh trở lại cho riêng tôi.

Hai echo --EOF--lệnh trong dòng được gửi đến trình bao ở đó để đảm bảo rằng đầu ra từ lệnh được kết thúc --EOF--ngay cả khi kết quả của một lỗi từ lệnh.

Tất nhiên, cách tiếp cận này có những hạn chế của nó. Những hạn chế này bao gồm:

  • nếu tôi nhập một lệnh đang chờ người dùng nhập (ví dụ: một trình bao khác), chương trình sẽ bị treo,
  • nó giả định rằng mỗi quá trình chạy bởi shell kết thúc đầu ra của nó bằng một dòng mới,
  • nó sẽ hơi bối rối nếu lệnh đang được chạy bởi trình bao xảy ra để viết ra một dòng --EOF--.
  • bashbáo lỗi cú pháp và thoát ra nếu bạn nhập một số văn bản với một số không khớp ).

Những điểm này có thể không quan trọng đối với bạn nếu bất cứ điều gì bạn đang nghĩ về việc chạy như một tác vụ đã lên lịch sẽ bị giới hạn trong một lệnh hoặc một nhóm lệnh nhỏ sẽ không bao giờ hoạt động theo những cách bệnh lý như vậy.

CHỈNH SỬA : cải thiện xử lý thoát và các thay đổi nhỏ khác sau khi chạy phần mềm này trên Linux.


Cảm ơn bạn đã trả lời toàn diện Tuy nhiên, tôi nghĩ rằng tôi đã xác định được trường hợp thực sự cho các vấn đề của mình. Tôi đã chú ý đến bài đăng của bạn. stackoverflow.com/questions/3645889/… . Cảm ơn bạn.
James moore

1
thay thế / bin / bash bằng cmd không thực sự hoạt động giống nhau, vì tôi đã tạo chương trình làm như vậy nhưng có vấn đề với bash. Trong mycase, việc mở ba luồng riêng biệt cho mỗi đầu vào / đầu ra / lỗi hoạt động tốt nhất mà không gặp vấn đề gì đối với các lệnh tương tác phiên dài.
Johnydep


@AlexMills: nhận xét thứ hai của bạn có nghĩa là bạn đã giải quyết được vấn đề của mình? Không có nơi nào đủ chi tiết trong nhận xét đầu tiên của bạn để nói vấn đề là gì, cũng như lý do tại sao bạn 'đột nhiên' nhận được ngoại lệ.
Luke Woodward

@Luke, liên kết tôi cung cấp ngay trên bình luận của bạn giải quyết vấn đề của tôi
Alexander Mills

4

Tôi nghĩ rằng bạn có thể sử dụng chuỗi như Demon-thread để đọc đầu vào của bạn và trình đọc đầu ra của bạn sẽ ở trong vòng lặp while trong chuỗi chính để bạn có thể đọc và viết cùng một lúc. Bạn có thể sửa đổi chương trình của mình như sau:

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

và bạn có thể đọc sẽ giống như trên, tức là

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

làm cho người viết của bạn là cuối cùng nếu không nó sẽ không thể truy cập được bởi lớp bên trong.


1

Bạn có writer.close();trong mã của bạn. Vì vậy, bash nhận EOF trên nó stdinvà thoát ra. Sau đó, bạn nhận được Broken pipekhi cố gắng đọc từ stdoutbash không còn tồn tại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.