package someTest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class ShellTest { public static void main(String[] args) { InputStreamReader stdISR = null; InputStreamReader errISR = null; Process process = null; String command = "/home/Lance/workspace/someTest/testbash.sh"; try { process = Runtime.getRuntime().exec(command); int exitValue = process.waitFor(); String line = null; stdISR = new InputStreamReader(process.getInputStream()); BufferedReader stdBR = new BufferedReader(stdISR); while ((line = stdBR.readLine()) != null) { System.out.println("STD line:" + line); } errISR = new InputStreamReader(process.getErrorStream()); BufferedReader errBR = new BufferedReader(errISR); while ((line = errBR.readLine()) != null) { System.out.println("ERR line:" + line); } } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { try { if (stdISR != null) { stdISR.close(); } if (errISR != null) { errISR.close(); } if (process != null) { process.destroy(); } } catch (IOException e) { System.out.println("正式执行命令:" + command + "有IO异常"); } } } }
testbash.sh
#!/bin/bash
echo pwd
输出结果为:
STD line:/home/Lance/workspace/someTest
Java在执行Runtime.getRuntime().exec(command)之后,Linux会创建一个进程,该进程与JVM进程建立三个管道连接,标准输入流、标准输出流、标准错误流。
上述代码,依次读取标准输出流和标准错误流,在shell给出“退出信号”后,做了相应的清理工作。
对于一般场景来说,这段代码可以凑合用了。但是,在实际场景中,会有以下几个“陷阱”。
一. 当标准输出流或标准错误流非常庞大的时候,会出现调用waitFor方法卡死的bug。
真实的环境中,当标准输出在10000行左右的时候,就会出现卡死的情况。
原因分析:假设linux进程不断向标准输出流和标准错误流写数据,而JVM却不读取,数据会暂存在linux缓存区,当缓存区存满之后导致该进程无法继续写数据,会僵死,导致java进程会卡死在waitFor()处,永远无法结束。
解决方式:由于标准输出和错误输出都会向Linux缓存区写数据,而脚本如何输出这两种流是Java端不能确定的。为了不让shell脚本的子进程卡死,这两种输出需要分别读取,而且不能互相影响。所以必须新开两个线程来进行读取。
我开始的实现如下:
package someTest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
public class CommandStreamGobbler extends Thread {
private InputStream is;
private String command;
private String prefix = "";
private boolean readFinish = false;
private boolean ready = false;
private List<String> infoList = new LinkedList<String>();
CommandStreamGobbler(InputStream is, String command, String prefix) {
this.is = is;
this.command = command;
this.prefix = prefix;
}
public void run() {
InputStreamReader isr = null;
try {
isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
ready = true;
while ((line = br.readLine()) != null) {
infoList.add(line);
System.out.println(prefix + " line: " + line);
}
} catch (IOException ioe) {
System.out.println("正式执行命令:" + command + "有IO异常");
} finally {
try {
if (isr != null) {