ประการแรกฉันอยากจะแนะนำให้เปลี่ยนบรรทัด
Process process = Runtime.getRuntime ().exec ("/bin/bash");
ด้วยเส้น
ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();
ProcessBuilder เป็นของใหม่ใน Java 5 และทำให้การรันกระบวนการภายนอกง่ายขึ้น ในความคิดของฉันการปรับปรุงที่สำคัญที่สุดRuntime.getRuntime().exec()
คือช่วยให้คุณสามารถเปลี่ยนเส้นทางข้อผิดพลาดมาตรฐานของกระบวนการย่อยไปยังเอาต์พุตมาตรฐานได้ ซึ่งหมายความว่าคุณมีเพียงหนึ่งที่InputStream
จะอ่านจาก ก่อนหน้านี้คุณจำเป็นต้องมีเธรดสองเธรดที่แยกจากกันการอ่านหนึ่งรายการstdout
และการอ่านหนึ่งจากstderr
เพื่อหลีกเลี่ยงการเติมบัฟเฟอร์ข้อผิดพลาดมาตรฐานในขณะที่บัฟเฟอร์เอาต์พุตมาตรฐานว่างเปล่า (ทำให้กระบวนการลูกหยุดทำงาน) หรือในทางกลับกัน
ถัดไปลูป (ซึ่งคุณมีสอง)
while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}
ออกก็ต่อเมื่อreader
ซึ่งอ่านจากเอาต์พุตมาตรฐานของกระบวนการส่งกลับ end-of-file สิ่งนี้จะเกิดขึ้นเมื่อbash
กระบวนการออกเท่านั้น มันจะไม่ส่งคืน end-of-file หากเกิดขึ้นในปัจจุบันโดยจะไม่มีเอาต์พุตจากกระบวนการอีกต่อไป แต่จะรอบรรทัดผลลัพธ์ถัดไปจากกระบวนการและไม่กลับมาจนกว่าจะมีบรรทัดถัดไปนี้
เนื่องจากคุณกำลังส่งอินพุตสองบรรทัดไปยังกระบวนการก่อนที่จะถึงลูปนี้ลูปแรกของทั้งสองนี้จะหยุดทำงานหากกระบวนการยังไม่ออกหลังจากอินพุตสองบรรทัดนี้ มันจะนั่งรออีกบรรทัดที่จะอ่าน แต่จะไม่มีบรรทัดอื่นให้อ่าน
ฉันรวบรวมซอร์สโค้ดของคุณ (ฉันใช้ Windows ในขณะนี้ดังนั้นฉันจึงแทนที่/bin/bash
ด้วยcmd.exe
แต่หลักการควรจะเหมือนกัน) และฉันพบว่า:
- หลังจากพิมพ์สองบรรทัดผลลัพธ์จากสองคำสั่งแรกจะปรากฏขึ้น แต่จากนั้นโปรแกรมก็หยุดทำงาน
- ถ้าฉันพิมพ์พูด
echo test
แล้วexit
โปรแกรมจะทำให้มันไม่อยู่ในลูปแรกตั้งแต่cmd.exe
ออกจากกระบวนการ จากนั้นโปรแกรมจะขออินพุตบรรทัดอื่น (ซึ่งถูกเพิกเฉย) ข้ามตรงไปยังลูปที่สองเนื่องจากกระบวนการลูกได้ออกไปแล้วและออกจากตัวเอง
- ถ้าฉันพิมพ์
exit
แล้วecho test
ฉันจะได้รับ IOException บ่นว่าท่อถูกปิด สิ่งนี้เป็นสิ่งที่คาดหวัง - อินพุตบรรทัดแรกทำให้กระบวนการออกและไม่มีที่ไหนที่จะส่งบรรทัดที่สอง
ฉันเคยเห็นกลเม็ดที่ทำสิ่งที่คล้ายกับสิ่งที่คุณต้องการในโปรแกรมที่ฉันเคยทำ โปรแกรมนี้เก็บไว้รอบ ๆ เชลล์จำนวนมากรันคำสั่งในนั้นและอ่านเอาต์พุตจากคำสั่งเหล่านี้ เคล็ดลับที่ใช้คือเขียนบรรทัด 'magic' ที่ทำเครื่องหมายจุดสิ้นสุดของเอาต์พุตของคำสั่งเชลล์และใช้เพื่อกำหนดว่าเมื่อใดที่เอาต์พุตจากคำสั่งที่ส่งไปยังเชลล์เสร็จสิ้น
ฉันเอารหัสของคุณและฉันแทนที่ทุกอย่างหลังบรรทัดที่กำหนดให้writer
ด้วยลูปต่อไปนี้:
while (scan.hasNext()) {
String input = scan.nextLine();
if (input.trim().equals("exit")) {
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;
}
}
หลังจากทำสิ่งนี้ฉันสามารถเรียกใช้คำสั่งสองสามคำสั่งได้อย่างน่าเชื่อถือและให้ผลลัพธ์จากแต่ละรายการกลับมาหาฉันทีละคำ
echo --EOF--
คำสั่งสองคำสั่งในบรรทัดที่ส่งไปยังเชลล์จะอยู่ที่นั่นเพื่อให้แน่ใจว่าเอาต์พุตจากคำสั่งนั้นถูกยกเลิก--EOF--
แม้จะเป็นผลลัพธ์ของข้อผิดพลาดจากคำสั่ง
แน่นอนว่าแนวทางนี้มีข้อ จำกัด ข้อ จำกัด เหล่านี้ ได้แก่ :
- ถ้าฉันป้อนคำสั่งที่รอการป้อนข้อมูลของผู้ใช้ (เช่นเชลล์อื่น) โปรแกรมจะหยุดทำงาน
- สมมติว่าแต่ละกระบวนการที่รันโดยเชลล์สิ้นสุดเอาต์พุตด้วยการขึ้นบรรทัดใหม่
--EOF--
มันได้รับบิตสับสนถ้าคำสั่งที่ถูกดำเนินการโดยเปลือกที่เกิดขึ้นกับเขียนออกบรรทัด
bash
)
รายงานข้อผิดพลาดทางไวยากรณ์และออกถ้าคุณใส่ข้อความบางส่วนกับที่ไม่มีใครเทียบ
ประเด็นเหล่านี้อาจไม่สำคัญสำหรับคุณหากสิ่งที่คุณคิดจะทำงานตามกำหนดเวลาจะถูก จำกัด ไว้ที่คำสั่งหรือชุดคำสั่งเล็ก ๆ ซึ่งจะไม่ทำงานในรูปแบบทางพยาธิวิทยาเช่นนั้น
แก้ไข : ปรับปรุงการจัดการการออกและการเปลี่ยนแปลงเล็กน้อยอื่น ๆ หลังจากเรียกใช้สิ่งนี้บน Linux