Sự khác biệt giữa ProcessBuilder và Runtime.exec ()


96

Tôi đang cố gắng thực thi một lệnh bên ngoài từ mã java, nhưng có một sự khác biệt mà tôi nhận thấy giữa Runtime.getRuntime().exec(...)new ProcessBuilder(...).start().

Khi sử dụng Runtime:

Process p = Runtime.getRuntime().exec(installation_path + 
                                       uninstall_path + 
                                       uninstall_command + 
                                       uninstall_arguments);
p.waitFor();

exitValue là 0 và lệnh được kết thúc là ok.

Tuy nhiên, với ProcessBuilder:

Process p = (new ProcessBuilder(installation_path +    
                                 uninstall_path +
                                 uninstall_command,
                                 uninstall_arguments)).start();
p.waitFor();

giá trị thoát là 1001 và lệnh kết thúc ở giữa, mặc dù waitFortrả về.

Tôi nên làm gì để khắc phục sự cố với ProcessBuilder?

Câu trả lời:


99

Các quá tải khác nhau của Runtime.getRuntime().exec(...)lấy một mảng chuỗi hoặc một chuỗi đơn. Quá tải chuỗi đơn của exec()sẽ chia nhỏ chuỗi đó thành một mảng đối số, trước khi chuyển mảng chuỗi vào một trong các exec()quá tải chiếm một mảng chuỗi. Mặt ProcessBuilderkhác, các hàm tạo chỉ lấy một mảng varargs các chuỗi hoặc một Listchuỗi, trong đó mỗi chuỗi trong mảng hoặc danh sách được giả định là một đối số riêng lẻ. Dù bằng cách nào, các đối số thu được sau đó được nối với nhau thành một chuỗi được chuyển đến HĐH để thực thi.

Vì vậy, ví dụ, trên Windows,

Runtime.getRuntime().exec("C:\DoStuff.exe -arg1 -arg2");

sẽ chạy một DoStuff.exechương trình với hai đối số đã cho. Trong trường hợp này, dòng lệnh được mã hóa và kết hợp lại với nhau. Tuy nhiên,

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");

sẽ không thành công, trừ khi có một chương trình có tên DoStuff.exe -arg1 -arg2trong đó C:\. Điều này là do không có mã hóa: lệnh chạy được cho là đã được mã hóa. Thay vào đó, bạn nên sử dụng

ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe", "-arg1", "-arg2");

Hay cách khác

List<String> params = java.util.Arrays.asList("C:\DoStuff.exe", "-arg1", "-arg2");
ProcessBuilder b = new ProcessBuilder(params);

nó vẫn không hoạt động: List <String> params = java.util.Arrays.asList (install_path + uninstall_path + uninstall_command, uninstall_arguments); Process qq = new ProcessBuilder (params) .start ();
dạ tiệc

7
Tôi không thể tin rằng việc ghép dữ liệu chuỗi này có ý nghĩa gì: "install_path + uninstall_path + uninstall_command".
Thiên thần O'Sphere

8
Runtime.getRuntime (). Execute (...) KHÔNG gọi một trình bao trừ khi được chỉ định rõ ràng bởi lệnh. Đó là một điều tốt liên quan đến vấn đề lỗi "Shellshock" gần đây. Câu trả lời này gây hiểu lầm, vì nó nói rằng cmd.exe hoặc tương đương (tức là / bin / bash trên unix) sẽ được chạy, điều này dường như không đúng với trường hợp này. Thay vào đó, mã hóa được thực hiện bên trong môi trường Java.
Stefan Paul Noack

@ noah1989: cảm ơn bạn đã phản hồi. Tôi đã cập nhật câu trả lời của mình để (hy vọng) làm rõ mọi thứ và đặc biệt là xóa mọi đề cập đến shell hoặc cmd.exe.
Luke Woodward

phân tích cú pháp cho exec không hoạt động hoàn toàn giống như một phiên bản tham số, hoặc, mà đã cho tôi một vài ngày để tìm ra ...
Drew Delano

18

Xem cách Runtime.getRuntime().exec()chuyển lệnh String đến ProcessBuilder. Nó sử dụng một trình mã hóa và phát nổ lệnh thành các mã thông báo riêng lẻ, sau đó gọi ra exec(String[] cmdarray, ......)cấu trúc nào a ProcessBuilder.

Nếu bạn tạo chuỗi ProcessBuilderbằng một mảng thay vì một chuỗi, bạn sẽ nhận được kết quả tương tự.

Hàm ProcessBuildertạo nhận một String...vararg, vì vậy việc chuyển toàn bộ lệnh dưới dạng một Chuỗi đơn có tác dụng giống như việc gọi lệnh đó trong dấu ngoặc kép trong một thiết bị đầu cuối:

shell$ "command with args"

14

Không có sự khác biệt giữa ProcessBuilder.start()Runtime.exec()bởi vì việc triển khai Runtime.exec()là:

public Process exec(String command) throws IOException {
    return exec(command, null, null);
}

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

public Process exec(String[] cmdarray, String[] envp, File dir)
    throws IOException {
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}

Vì vậy, mã:

List<String> list = new ArrayList<>();
new StringTokenizer(command)
.asIterator()
.forEachRemaining(str -> list.add((String) str));
new ProcessBuilder(String[])list.toArray())
            .environment(envp)
            .directory(dir)
            .start();

phải giống như:

Runtime.exec(command)

Cảm ơn dave_thompson_085 đã bình luận


2
Nhưng Q không gọi phương pháp đó. Nó (gián tiếp) gọi public Process exec(String command, String[] envp, File dir)- StringNOT String[]- gọi StringTokenizervà đặt các mã thông báo vào một mảng sau đó được chuyển (gián tiếp) đến ProcessBuilder, đó là một sự khác biệt như được nêu chính xác bởi ba câu trả lời từ 7 năm trước.
dave_thompson_085

Không quan trọng câu hỏi bao nhiêu tuổi. Nhưng tôi cố gắng sửa câu trả lời.
Eugene Lopatkin

Tôi không thể đặt môi trường cho ProcessBuilder. Tôi chỉ có thể có được môi trường ...
ilke Muhtaroglu

thấy docs.oracle.com/javase/7/docs/api/java/lang/... với môi trường thiết lập sau khi nhận được chúng thông qua phương pháp môi trường ...
ilke Muhtaroglu

Nếu bạn nhìn kỹ hơn, bạn có thể thấy rằng môi trường theo mặc định là null.
Eugene Lopatkin

14

Có một sự khác biệt.

  • Các Runtime.exec(String)phương pháp có một chuỗi lệnh duy nhất mà nó tách ra thành một câu lệnh và một chuỗi các đối số.

  • Hàm ProcessBuildertạo nhận một mảng chuỗi (varargs). Chuỗi đầu tiên là tên lệnh và phần còn lại của chúng là các đối số. (Có một hàm tạo thay thế lấy một danh sách các chuỗi, nhưng không một hàm tạo nào nhận một chuỗi đơn bao gồm lệnh và các đối số.)

Vì vậy, những gì bạn đang yêu cầu ProcessBuilder làm là thực hiện một "lệnh" có tên có khoảng trắng và các thứ khác trong đó. Tất nhiên, hệ điều hành không thể tìm thấy lệnh có tên đó và việc thực thi lệnh không thành công.


Không, không có sự khác biệt. Runtime.exec (String) là một phím tắt cho ProcessBuilder. Có các trình xây dựng khác được hỗ trợ.
marcolopes

2
Bạn không chính xác. Đọc mã nguồn! Runtime.exec(cmd)hiệu quả là một phím tắt cho Runtime.exec(cmd.split("\\s+")). Các ProcessBuilderlớp không có một constructor đó là một tương đương trực tiếp Runtime.exec(cmd). Đây là điểm tôi đang thực hiện trong câu trả lời của mình.
Stephen C

1
Trong thực tế, nếu bạn tạo một ProcessBuilder như thế này: new ProcessBuilder("command arg1 arg2"), các start()cuộc gọi sẽ không làm những gì bạn mong đợi. Nó có thể sẽ thất bại và chỉ thành công nếu bạn có một lệnh có dấu cách trong tên của nó. Đây chính xác là vấn đề mà OP đang thắc mắc!
Stephen C
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.