第一次代码是这样的
static String interpreter = "/bin/sh";
public static String runSimpleCommand(String command) throws IOException {
String[] cmd = {interpreter,"-c",command};
Process process = Runtime.getRuntime().exec(cmd);
String result = RuntimeUtil.getResult(process);
return result;
}
一般情况下,以上代码执行是没有问题的,执行命令,读取返回结果。 但是如果命令执行过程中异常了,或者进程不止写了标准输出,还有标准错误输出,那么RuntimeUtil.getResult(process)将无法读取到错误信息,于是程序变成了下面这样。
public static ShellResult runSimpleCommand(String command) throws IOException {
String[] cmd = {interpreter,"-c",command};
Process process = Runtime.getRuntime().exec(cmd);
String stdout = RuntimeUtil.getResult(process);
String stderr = RuntimeUtil.getErrorResult(process);
return new ShellResult(stdout,stderr);
}
@Data
@AllArgsConstructor
public class ShellResult{
private String stdout;
private String stderr;
public String std(){
return this.stdout+"\r\n"+this.stderr;
}
}
这时又出现了问题,代码居然执行抛出了异常! debug后才发现,hutool的RuntimeUtil获取返回值源码是这样的
public static String getResult(Process process, Charset charset) {
InputStream in = null;
try {
in = process.getInputStream();
return IoUtil.read(in, charset);
} finally {
IoUtil.close(in);
destroy(process);
}
}
原来hutool这货在读取结果后居然字节关闭流,destroy了这个process,难怪会抛异常。还是太相信这些工具类了,难道hutool的开发者就没想过,有些时候还需要同时读stderr吗。既然发现了问题。那这次就来自己实现流的读取,这里没有使用一次性读取inputstream,而是使用按行读取原因是因为业务需要,需要把标准输出过程数据写入远端socket中,这里代码就略去了。
public ShellResult runSimpleCommand(String command) throws IOException {
String[] cmd = {interpreter,"-c",command};
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader processBr = new BufferedReader(new InputStreamReader(process.getInputStream(), "utf-8"));
BufferedReader processErrorBr = new BufferedReader(new InputStreamReader(process.getErrorStream(), "utf-8"));
String processLine;
//--标准输出
StringBuilder stdout = new StringBuilder();
try {
while ((processLine = processBr.readLine()) != null) {
stdout.append(processLine+"\r\n");
}
} catch (IOException e) {
log.debug("读stdout异常",e);
}
//--标准err输出
StringBuilder stderr = new StringBuilder();
try {
while ((processLine = processErrorBr.readLine()) != null) {
stderr.append(processLine+"\r\n");
}
} catch (IOException e) {
log.debug("读stderr异常",e);
}
ShellResult shellResult = new ShellResult(stdout.toString(), stderr.toString());
return shellResult;
}
如果业务不需要过程输出,只要结果的话,当然文件流的长度是可预见的,直接读取效率更高,见代码。
public static byte[] readBytes(InputStream in, boolean isClose) throws IOException {
// 文件流的长度是可预见的,此时直接读取效率更高
final byte[] result;
try {
final int available = in.available();
result = new byte[available];
final int readLength = in.read(result);
if (readLength != available) {
throw new IOException(StrUtil.format("File length is [{}] but read [{}]!", available, readLength));
}
} finally {
if (isClose) {
close(in);
}
}
return result;
}
后记
期间报错,一度以为process获取标准输出也是通过读取/proc/{pid}/fd/1这个文件,以为命令执行时间太短,导致读取完stdout后,进程销毁导致无法读取stderr,还把读取方式一度改为并发读取。
public ShellResult runSimpleCommandMultiGetResult(String command) throws IOException {
String[] cmd = {interpreter,"-c",command};
Process process = Runtime.getRuntime().exec(cmd);
String result = "";
String errors = "";
Callable<String> resultCall = () -> {
String result1 = RuntimeUtil.getResult(process);
return result1;
};
Callable<String> errorsCall = () -> {
String errors1 = RuntimeUtil.getErrorResult(process);
return errors1;
};
FutureTask<String> resultTask = new FutureTask<>(resultCall);
FutureTask<String> errorResultTask = new FutureTask<>(errorsCall);
socketPool.submit(resultTask);
socketPool.submit(errorResultTask);
try {
result = resultTask.get();
} catch (Exception e) {
log.debug("读stdout异常",e);
}
try {
errors = errorResultTask.get();
} catch (Exception e) {
log.debug("读stderr异常",e);
}
ShellResult shellResult = new ShellResult(result, errors);
return shellResult;
}
其实在这个process对象没有被销毁,流没有关闭的情况下,一直都可以读取,读取流的方式也不是通过读取pid下的文件。
本文由 转啊转 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2020/04/30 17:12
学习了,受益匪浅