จะทำให้ไปป์ทำงานกับ Runtime.exec () ได้อย่างไร?


104

พิจารณารหัสต่อไปนี้:

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

ผลลัพธ์ของโปรแกรมคือ:

/etc:
adduser.co

แน่นอนว่าเมื่อฉันเรียกใช้จากเชลล์มันทำงานได้ตามที่คาดไว้:

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

ผู้ฝึกงานบอกฉันว่าเนื่องจากพฤติกรรมของท่อไม่ใช่การข้ามแพลตฟอร์มจิตใจที่ยอดเยี่ยมที่ทำงานในโรงงาน Java ที่ผลิต Java ไม่สามารถรับประกันได้ว่าไปป์ทำงานได้

ฉันจะทำเช่นนี้ได้อย่างไร?

ฉันจะไม่ทำการแยกวิเคราะห์ทั้งหมดของฉันโดยใช้โครงสร้าง Java มากกว่าgrepและsedเพราะถ้าฉันต้องการเปลี่ยนภาษาฉันจะถูกบังคับให้เขียนรหัสการแยกวิเคราะห์ของฉันใหม่ในภาษานั้นซึ่งไม่ต้องทำเลย

ฉันจะทำให้ Java ทำการไพพ์และการเปลี่ยนเส้นทางเมื่อเรียกคำสั่งเชลล์ได้อย่างไร


ฉันเห็นแบบนี้: หากคุณใช้การจัดการสตริง Java แบบเนทีฟคุณจะรับประกันความสามารถในการพกพาของแอป Java ไปยังแพลตฟอร์มทั้งหมดที่รองรับ Java OTOH หากคุณใช้คำสั่งเชลล์การเปลี่ยนภาษาจาก Java จะง่ายกว่าแต่จะใช้ได้เฉพาะเมื่อคุณอยู่บนแพลตฟอร์ม POSIX ไม่กี่คนที่เปลี่ยนภาษาของแอปแทนที่จะเป็นแพลตฟอร์มที่แอปทำงาน นั่นเป็นเหตุผลที่ฉันคิดว่าเหตุผลของคุณค่อนข้างน่าสงสัย
Janus Troelsen

ในกรณีเฉพาะของสิ่งที่เรียบง่ายเช่นcommand | grep fooคุณจะดีกว่ามากเพียงแค่เรียกใช้commandและทำการกรองใน Java มันทำให้โค้ดของคุณค่อนข้างซับซ้อนขึ้น แต่คุณยังลดการใช้ทรัพยากรโดยรวมและพื้นผิวการโจมตีลงอย่างมาก
tripleee

คำตอบ:


183

เขียนสคริปต์และเรียกใช้สคริปต์แทนคำสั่งแยกต่างหาก

ท่อเป็นส่วนหนึ่งของเปลือกดังนั้นคุณสามารถทำสิ่งนี้ได้:

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

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

@Kaj เกิดอะไรขึ้นถ้าคุณต้องการเพิ่มตัวเลือกให้กับlsie ls -lrt?
kaustav datta

8
@Kaj ฉันเห็นว่าคุณพยายามใช้ -c เพื่อระบุสตริงคำสั่งให้กับเชลล์ แต่ฉันไม่เข้าใจว่าทำไมคุณต้องสร้างอาร์เรย์สตริงนี้แทนที่จะเป็นสตริงเดียว?
David Doria

2
หากมีใครกำลังมองหาandroidเวอร์ชันที่นี่ให้ใช้/system/bin/shแทน
Nexen

25

ฉันพบปัญหาที่คล้ายกันใน Linux ยกเว้นว่ามันคือ "ps -ef | grep someprocess"
อย่างน้อยด้วย "ls" คุณมีการแทนที่ Java ที่ไม่ขึ้นกับภาษา (แม้ว่าจะช้ากว่า) เช่น.:

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

สำหรับ "ps" จะยากกว่าเล็กน้อยเนื่องจาก Java ดูเหมือนจะไม่มี API สำหรับมัน

ฉันได้ยินมาว่า Sigar อาจช่วยเราได้ที่ https://support.hyperic.com/display/SIGAR/Home

อย่างไรก็ตามวิธีแก้ปัญหาที่ง่ายที่สุด (ตามที่ Kaj ชี้ให้เห็น) คือดำเนินการคำสั่ง piped เป็นสตริงอาร์เรย์ นี่คือรหัสเต็ม:

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

สาเหตุที่อาร์เรย์ String ทำงานร่วมกับไปป์ในขณะที่สตริงเดี่ยวไม่ได้ ... เป็นหนึ่งในความลึกลับของจักรวาล (โดยเฉพาะอย่างยิ่งหากคุณยังไม่ได้อ่านซอร์สโค้ด) ฉันสงสัยว่าเป็นเพราะเมื่อ exec ได้รับสตริงเดียวมันจะแยกวิเคราะห์ก่อน (ในแบบที่เราไม่ชอบ) ในทางตรงกันข้ามเมื่อ exec ได้รับสตริงอาร์เรย์มันก็ส่งต่อไปยังระบบปฏิบัติการโดยไม่ต้องแยกวิเคราะห์

จริงๆแล้วถ้าเราใช้เวลาว่างจากวันที่วุ่นวายและดูซอร์สโค้ด (ที่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 ) เราพบว่านั่นคือสิ่งที่เกิดขึ้น:

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

ฉันเห็นพฤติกรรมเดียวกันกับการเปลี่ยนเส้นทางนั่นคืออักขระ ">" การวิเคราะห์เพิ่มเติมเกี่ยวกับอาร์เรย์สตริงทำให้กระจ่าง
kris

คุณไม่จำเป็นต้องใช้เวลาหนึ่งวันจากตารางงานที่ยุ่งของคุณมันถูกเขียนต่อหน้าคุณ docs / api / java / lang / Runtime.html # exec (java.lang.String)
gpasch

6

สร้าง Runtime เพื่อรันแต่ละกระบวนการ รับ OutputStream จาก Runtime แรกและคัดลอกไปยัง InputStream จากอันที่สอง


1
มันคุ้มค่าที่จะชี้ให้เห็นว่าสิ่งนี้มีประสิทธิภาพน้อยกว่า os-native ไปป์เนื่องจากคุณกำลังคัดลอกหน่วยความจำไปยังพื้นที่แอดเดรสของกระบวนการ Java จากนั้นกลับออกไปยังกระบวนการต่อไป
Tim Boudreau

0

@Kaj คำตอบที่ยอมรับสำหรับ linux นี่คือสิ่งที่เทียบเท่าสำหรับ Windows:

String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.