Làm thế nào để làm cho đường ống hoạt động với Runtime.exec ()?


104

Hãy xem xét đoạn mã sau:

String commandf = "ls /etc | grep release";

try {

    // Execute the command and wait for it to complete
    Process child = Runtime.getRuntime().exec(commandf);
    child.waitFor();

    // Print the first 16 bytes of its output
    InputStream i = child.getInputStream();
    byte[] b = new byte[16];
    i.read(b, 0, b.length); 
    System.out.println(new String(b));

} catch (IOException e) {
    e.printStackTrace();
    System.exit(-1);
}

Đầu ra của chương trình là:

/etc:
adduser.co

Tất nhiên, khi tôi chạy từ shell, nó hoạt động như mong đợi:

poundifdef@parker:~/rabbit_test$ ls /etc | grep release
lsb-release

Các internets nói với tôi rằng, do hành vi của pipe không phải là đa nền tảng, những bộ óc xuất sắc làm việc trong nhà máy Java sản xuất Java không thể đảm bảo rằng pipe hoạt động.

Tôi có thể làm cái này như thế nào?

Tôi sẽ không thực hiện tất cả quá trình phân tích cú pháp của mình bằng cách sử dụng các cấu trúc Java thay vì grepsed, bởi vì nếu tôi muốn thay đổi ngôn ngữ, tôi sẽ buộc phải viết lại mã phân tích cú pháp của mình bằng ngôn ngữ đó, điều này hoàn toàn không nên.

Làm cách nào tôi có thể làm cho Java thực hiện đường ống và chuyển hướng khi gọi các lệnh shell?


Tôi thấy nó như thế này: Nếu bạn làm điều đó với xử lý chuỗi Java gốc, bạn được đảm bảo tính khả chuyển của ứng dụng Java tới tất cả các nền tảng mà Java hỗ trợ. OTOH, nếu bạn làm điều đó với các lệnh shell, việc thay đổi ngôn ngữ từ Java sẽ dễ dàng hơn , nhưng sẽ chỉ hoạt động khi bạn đang sử dụng nền tảng POSIX. Ít người thay đổi ngôn ngữ của ứng dụng hơn là nền tảng mà ứng dụng chạy trên đó. Đó là lý do tại sao tôi nghĩ rằng lý do của bạn là một chút tò mò.
Janus Troelsen

Trong trường hợp cụ thể của một cái gì đó đơn giản như command | grep foobạn sẽ tốt hơn nhiều chỉ cần chạy commandvà thực hiện lọc nguyên bản trong Java. Nó làm cho mã của bạn phức tạp hơn một chút nhưng bạn cũng giảm đáng kể mức tiêu thụ tài nguyên và bề mặt tấn công.
tripleee

Câu trả lời:


182

Viết một tập lệnh và thực thi tập lệnh thay vì các lệnh riêng biệt.

Ống là một phần của vỏ, vì vậy bạn cũng có thể làm như sau:

String[] cmd = {
"/bin/sh",
"-c",
"ls /etc | grep release"
};

Process p = Runtime.getRuntime().exec(cmd);

@Kaj Điều gì sẽ xảy ra nếu bạn muốn thêm các tùy chọn vào lstức là ls -lrt?
kaustav datta

8
@Kaj Tôi thấy rằng bạn đang cố gắng sử dụng -c để chỉ định một chuỗi lệnh cho trình bao, nhưng tôi không hiểu tại sao bạn phải đặt đây là một mảng chuỗi thay vì chỉ một chuỗi?
David Doria

2
Nếu ai đó đang tìm kiếm androidphiên bản ở đây, thì hãy sử dụng /system/bin/shthay thế
Nexen

25

Tôi đã gặp sự cố tương tự trong Linux, ngoại trừ nó là "ps -ef | grep someprocess".
Ít nhất với "ls", bạn có một sự thay thế Java không phụ thuộc vào ngôn ngữ (mặc dù chậm hơn). Ví dụ.:

File f = new File("C:\\");
String[] files = f.listFiles(new File("/home/tihamer"));
for (String file : files) {
    if (file.matches(.*some.*)) { System.out.println(file); }
}

Với "ps", nó khó hơn một chút, vì Java dường như không có API cho nó.

Tôi nghe nói rằng Sigar có thể giúp chúng tôi: https://support.hyperic.com/display/SIGAR/Home

Tuy nhiên, giải pháp đơn giản nhất (như Kaj đã chỉ ra) là thực hiện lệnh piped dưới dạng một mảng chuỗi. Đây là mã đầy đủ:

try {
    String line;
    String[] cmd = { "/bin/sh", "-c", "ps -ef | grep export" };
    Process p = Runtime.getRuntime().exec(cmd);
    BufferedReader in =
            new BufferedReader(new InputStreamReader(p.getInputStream()));
    while ((line = in.readLine()) != null) {
        System.out.println(line); 
    }
    in.close();
} catch (Exception ex) {
    ex.printStackTrace();
}

Về lý do tại sao mảng Chuỗi hoạt động với đường ống, trong khi một chuỗi đơn thì không ... đó là một trong những bí ẩn của vũ trụ (đặc biệt nếu bạn chưa đọc mã nguồn). Tôi nghi ngờ rằng đó là bởi vì khi thực thi được cung cấp một chuỗi đơn, nó sẽ phân tích cú pháp trước (theo cách mà chúng tôi không thích). Ngược lại, khi thực thi được cung cấp một mảng chuỗi, nó chỉ cần chuyển nó đến hệ điều hành mà không cần phân tích cú pháp.

Trên thực tế, nếu chúng ta dành thời gian trong ngày bận rộn và xem mã nguồn (tại http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/ Runtime.java # Runtime.exec% 28java.lang.String% 2Cjava.lang.String []% 2Cjava.io.File% 29 ), chúng tôi thấy đó chính xác là những gì đang xảy ra:

public Process  [More ...] exec(String command, String[] envp, File dir) 
          throws IOException {
    if (command.length() == 0)
        throw new IllegalArgumentException("Empty command");
    StringTokenizer st = new StringTokenizer(command);
    String[] cmdarray = new String[st.countTokens()];
    for (int i = 0; st.hasMoreTokens(); i++)
        cmdarray[i] = st.nextToken();
    return exec(cmdarray, envp, dir);
}

Tôi thấy hành vi tương tự với chuyển hướng, tức là ký tự '>'. Phân tích bổ sung này về mảng chuỗi thật kỳ diệu
kris

Bạn không cần phải mất một ngày trong lịch trình bận rộn của bạn - bằng văn bản trước mắt của bạn docs / api / java / lang / Runtime.html # exec (java.lang.String)
gpasch

6

Tạo Runtime để chạy từng quy trình. Lấy OutputStream từ Runtime đầu tiên và sao chép nó vào InputStream từ lần thứ hai.


1
Cần phải chỉ ra rằng điều này kém hiệu quả hơn nhiều so với đường ống os-native, vì bạn đang sao chép bộ nhớ vào không gian địa chỉ của quy trình Java và sau đó quay lại quy trình tiếp theo.
Tim Boudreau

0

Câu trả lời được chấp nhận @Kaj là dành cho linux. Đây là cái tương đương cho Windows:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
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.