ความแตกต่างระหว่าง ProcessBuilder และ Runtime.exec ()


97

ฉันกำลังพยายามเรียกใช้คำสั่งภายนอกจากรหัส java แต่มีความแตกต่างที่ฉันสังเกตเห็นระหว่างRuntime.getRuntime().exec(...)และnew ProcessBuilder(...).start().

เมื่อใช้Runtime:

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

exitValue คือ 0 และคำสั่งถูกยกเลิกตกลง

อย่างไรก็ตามด้วยProcessBuilder:

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

ค่าออกคือ 1001 และคำสั่งจะสิ้นสุดตรงกลางแม้ว่าจะwaitForส่งคืน

สิ่งที่ฉันควรทำอย่างไรเพื่อแก้ไขปัญหาด้วยProcessBuilder?

คำตอบ:


100

โอเวอร์โหลดต่างๆของRuntime.getRuntime().exec(...)ใช้อาร์เรย์ของสตริงหรือสตริงเดียว การโอเวอร์โหลดสตริงเดียวของexec()จะทำให้สตริงเป็นอาร์เรย์ของอาร์กิวเมนต์ก่อนที่จะส่งสตริงอาร์เรย์ไปยังexec()โอเวอร์โหลดที่รับสตริงอาร์เรย์ ProcessBuilderก่อสร้างในมืออื่น ๆ ที่ใช้เวลาเพียง varargs อาร์เรย์ของสตริงหรือListสตริงที่สตริงในอาร์เรย์หรือแต่ละรายการจะถือว่าเป็นข้อโต้แย้งของแต่ละบุคคล ไม่ว่าจะด้วยวิธีใดอาร์กิวเมนต์ที่ได้รับจะถูกรวมเข้ากับสตริงที่ส่งผ่านไปยัง OS เพื่อดำเนินการ

ตัวอย่างเช่นใน Windows

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

จะรันDoStuff.exeโปรแกรมด้วยอาร์กิวเมนต์ที่กำหนด ในกรณีนี้บรรทัดคำสั่งจะได้รับโทเค็นและนำกลับมารวมกัน อย่างไรก็ตาม

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

จะล้มเหลวจนกว่าจะมีเกิดขึ้นเป็นโปรแกรมที่มีชื่อเป็นในDoStuff.exe -arg1 -arg2 C:\เนื่องจากไม่มีการใช้โทเค็น: คำสั่งที่เรียกใช้จะถือว่าเป็นโทเค็นแล้ว คุณควรใช้ไฟล์

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

หรืออีกทางหนึ่ง

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

ยังใช้ไม่ได้: รายการ <String> params = java.util.Arrays.asList (installation_path + uninstall_path + uninstall_command, uninstall_arguments); ประมวลผล qq = ProcessBuilder ใหม่ (params) .start ();
สาว

7
ฉันไม่อยากเชื่อเลยว่าการต่อสายอักขระนี้เข้ากันได้ดี: "installation_path + uninstall_path + uninstall_command"
Angel O'Sphere

8
Runtime.getRuntime (). exec (... ) ไม่เรียกใช้เชลล์เว้นแต่จะระบุไว้อย่างชัดเจนโดยคำสั่ง นั่นเป็นสิ่งที่ดีเกี่ยวกับปัญหาข้อบกพร่อง "Shellshock" ล่าสุด คำตอบนี้ทำให้เข้าใจผิดเนื่องจากระบุว่าจะเรียกใช้ cmd.exe หรือเทียบเท่า (เช่น / bin / bash บน unix) ซึ่งดูเหมือนจะไม่เป็นเช่นนั้น แทนที่จะทำโทเค็นภายในสภาพแวดล้อม Java
Stefan Paul Noack

@ noah1989: ขอบคุณสำหรับคำติชม ฉันได้อัปเดตคำตอบของฉันเพื่อ (หวังว่า) จะชี้แจงสิ่งต่าง ๆ และโดยเฉพาะอย่างยิ่งลบการกล่าวถึงเชลล์หรือcmd.exe.
Luke Woodward

ตัวแยกวิเคราะห์สำหรับ exec ทำงานไม่เหมือนกับเวอร์ชันที่กำหนดพารามิเตอร์ซึ่งใช้เวลาสองสามวันในการคิดออก ...
Drew Delano

19

ดูว่าRuntime.getRuntime().exec()ส่งคำสั่ง String ไปยังไฟล์ProcessBuilder. ใช้โทเค็นและระเบิดคำสั่งลงในโทเค็นแต่ละรายการจากนั้นเรียกใช้exec(String[] cmdarray, ......)ซึ่งสร้างไฟล์ProcessBuilder.

หากคุณสร้างProcessBuilderด้วยอาร์เรย์ของสตริงแทนที่จะเป็นสตริงเดียวคุณจะได้ผลลัพธ์เดียวกัน

ตัวProcessBuilderสร้างใช้String...vararg ดังนั้นการส่งคำสั่งทั้งหมดเป็นสตริงเดียวจึงมีผลเหมือนกับการเรียกใช้คำสั่งนั้นในเครื่องหมายคำพูดในเทอร์มินัล:

shell$ "command with args"

16

ไม่มีความแตกต่างระหว่างProcessBuilder.start()และRuntime.exec()เนื่องจากการนำไปใช้Runtime.exec()คือ:

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

ดังนั้นรหัส:

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

ควรจะเหมือนกับ:

Runtime.exec(command)

ขอบคุณdave_thompson_085สำหรับความคิดเห็น


2
แต่ Q ไม่เรียกวิธีนั้น มัน (ทางอ้อม) เรียกpublic Process exec(String command, String[] envp, File dir)- Stringไม่ใช่String[]- ซึ่งเรียกStringTokenizerและวางโทเค็นในอาร์เรย์ซึ่งจะถูกส่งผ่าน (ทางอ้อม) ไปProcessBuilderซึ่งเป็นความแตกต่างตามที่ระบุไว้อย่างถูกต้องโดยคำตอบทั้งสามข้อเมื่อ 7 ปีที่แล้ว
dave_thompson_085

ไม่สำคัญว่าคำถามจะเก่าแค่ไหน แต่ฉันพยายามแก้ไขคำตอบ
Eugene Lopatkin

ฉันไม่สามารถตั้งค่าสภาพแวดล้อมสำหรับ ProcessBuilder ฉันได้ แต่สิ่งแวดล้อม ...
ilke Muhtaroglu

ดูdocs.oracle.com/javase/7/docs/api/java/lang/…เพื่อตั้งค่าสภาพแวดล้อมหลังจากได้รับผ่านวิธีสภาพแวดล้อม ...
ilke Muhtaroglu

หากคุณมองอย่างรอบคอบมากขึ้นคุณจะเห็นว่าสภาพแวดล้อมนั้นเป็นค่าเริ่มต้น
Eugene Lopatkin

15

ใช่มีความแตกต่าง

  • Runtime.exec(String)วิธีใช้สตริงคำสั่งเดียวว่ามันแยกออกเป็นคำสั่งและลำดับของการขัดแย้ง

  • ตัวProcessBuilderสร้างใช้อาร์เรย์ของสตริง (varargs) สตริงแรกคือชื่อคำสั่งและส่วนที่เหลือคืออาร์กิวเมนต์ (มีตัวสร้างทางเลือกที่รับรายการสตริง แต่ไม่มีที่ใช้สตริงเดียวที่ประกอบด้วยคำสั่งและอาร์กิวเมนต์)

ดังนั้นสิ่งที่คุณกำลังบอกให้ ProcessBuilder ทำคือเรียกใช้ "คำสั่ง" ที่มีชื่อมีช่องว่างและมีขยะอื่น ๆ อยู่ แน่นอนว่าระบบปฏิบัติการไม่พบคำสั่งที่มีชื่อนั้นและการดำเนินการคำสั่งล้มเหลว


ไม่มีไม่มีความแตกต่าง Runtime.exec (String) เป็นทางลัดสำหรับ ProcessBuilder มีคอนสตรัคเตอร์อื่น ๆ รองรับ
marcolopes

2
คุณไม่ถูกต้อง อ่านซอร์สโค้ด! ได้อย่างมีประสิทธิภาพทางลัดสำหรับRuntime.exec(cmd) ชั้นไม่ได้มีการสร้างที่เป็นเทียบเท่าโดยตรง นี่คือประเด็นที่ฉันทำในคำตอบของฉัน Runtime.exec(cmd.split("\\s+"))ProcessBuilderRuntime.exec(cmd)
Stephen C

1
ในความเป็นจริงถ้าคุณสร้างอินสแตนซ์ ProcessBuilder เช่นนี้new ProcessBuilder("command arg1 arg2")การstart()โทรจะไม่ทำตามที่คุณคาดหวัง มันอาจจะล้มเหลวและจะสำเร็จก็ต่อเมื่อคุณมีคำสั่งที่มีช่องว่างในชื่อ นี่คือปัญหาที่ OP กำลังถามถึง!
Stephen C
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.