1. Runtime 介绍
Runtime
是 Java 提供的一个启动子进程来执行命令的方式,它提供了 6 个重载的 exec
方法,用于单独启动一个子进程来执行命令或调用程序。
public Process exec(String command) throws IOException
public Process exec(String command, String[] envp) throws IOException
public Process exec(String command, String[] envp, File dir) throws IOException
public Process exec(String cmdarray[]) throws IOException
public Process exec(String[] cmdarray, String[] envp) throws IOException
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException
每一个方法最终都返回一个 Process
对象表示一个进程,从该对象中能够获取进程执行的退出状态码,标准输出流和标准错误流,进而获取进程执行的输出内容。
前 5 个方法都是间接调用最后一个方法 public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException
。该方法有 3 个参数:
-
cmdarray
命令字符串数组表示,例如执行命令echo "hello world"
,对应的参数为new String[]{"echo","hello world"}
-
envp
字符串数组(可以为null
),表示命令执行过程中设置的环境变量,格式为name=value
,例如:a=1
。当为null
时,(应该,但不一定,取决于 jvm 的实现)继承当前jvm
进程的环境变量。 -
dir
是一个File
对象(可以为null
),表示命令执行的工作目录。如果命令中有使用到类似于./
的相对路径,则该相对路径就是基于dir
的。当为null
时,工作目录(应该,但不一定,取决于 jvm 的实现)继承当前jvm
进程的工作目录。
2. 命令使用一个字符串和使用字符串数组的区别
public Process exec(String command, String[] envp, File dir) throws IOException
和public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException
的区别。
public Process exec(String command, String[] envp, File dir) throws IOException
的实现如下:
public Process exec(String command, String[] envp, File dir) throws IOException {
if (command.isEmpty())
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);
}
可以发现,它将 command
命令使用 StringTokenizer
以空格进行分割,然后组装成数组 cmdarray
,最终依然调用的是 exec(String[] cmdarray, String[] envp, File dir)
方法。
如果不仔细思考,可能觉得这两个重载方法没什么区别,exec(String[] cmdarray, String[] envp, File dir)
不是多此一举吗?
我们看一个在 linux 系统执行日期修改的命令:
date -s "2022-11-11 12:00:00"
很遗憾使用 exec(String command, String[] envp, File dir)
方法执行该命令无法成功:
//无法执行成功,会报错
Runtime.getRuntime().exec("date -s \"2022-11-11 12:00:00\"", null, null);
这是因为该方法将命令以空格进行分割然后组成数组进行执行,实际上在真正调用exec(cmdarray, envp, dir)
为:
Runtime.getRuntime().exec(new String[]{"date", "-s", "\"2022-11-11", "12:00:00\"", null, null);
它将参数 "2022-11-11 12:00:00"
分割成了两个参数 "2022-11-11
、 12:00:00"
。所以执行命令时就导致了报错。
现在应该理解了单字符串命令和命令数组的区别,总结如下:
-
如果执行的命令的参数中包含空格一定要使用
exec(String[] cmdarray, String[] envp, File dir)
,否则将导致命令执行失败。 - 如果执行的命令的参数中没有空格不会导致参数分裂,则两个方法都一样。
3. Runtime 工具类封装
下面是作者常用的 Runtime
工具类 SystemCommandUtil
:文章来源:https://www.toymoban.com/news/detail-469428.html
package com.cssth.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
* 系统命令执行
*
* @author geng
* @since 2022/3/22 9:49
*/
public final class SystemCommandUtil {
private final static Logger logger = LoggerFactory.getLogger(SystemCommandUtil.class);
/**
* 不要执行<code>cmd</code>等长时间不会退出的命令,否则会导致启动的子进程长时间不退出!
*
* @param command 命令
*/
public static void exec(String command) {
execWithExitCode(command);
}
public static int execWithExitCode(String command) {
CommandExecResult commandExecResult = execWithExitCodeAndResult(command);
return commandExecResult.exitCode;
}
public static CommandExecResult execWithExitCodeAndResult(String[] cmdarray) {
InputStream errorStream;
InputStream inputStream;
int exitCode = -1;
String stdinOutput = "";
String errorOutput = "";
try {
Process exec = Runtime.getRuntime().exec(cmdarray);
inputStream = exec.getInputStream();
stdinOutput = readStringFromInputStream(inputStream);
logger.warn("执行命令'{}'标准流输出:\n{}", cmdarray, stdinOutput);
//有些命令就算执行成功也会将输出放到错误流,所以这里也将错误流内容进行读取,便于调试
errorStream = exec.getErrorStream();
errorOutput = readStringFromInputStream(errorStream);
logger.warn("执行命令'{}'错误流输出:\n{}", cmdarray, errorOutput);
exitCode = exec.waitFor();
if (exitCode != 0) {
logger.warn("执行命令'{}'可能失败,退出code:{}!", Arrays.toString(cmdarray), exitCode);
} else {
logger.warn("执行命令'{}'成功,退出code:{}!", Arrays.toString(cmdarray), exitCode);
}
} catch (InterruptedException ie) {
logger.error("执行命令'{}'被中断!", Arrays.toString(cmdarray));
} catch (IOException e) {
logger.error("执行命令'" + Arrays.toString(cmdarray) + "'抛出异常!", e);
}
return new CommandExecResult(exitCode, stdinOutput, errorOutput);
}
public static CommandExecResult execWithExitCodeAndResult(String command) {
InputStream errorStream;
InputStream inputStream;
int exitCode = -1;
String stdinOutput = "";
String errorOutput = "";
try {
Process exec = Runtime.getRuntime().exec(command);
inputStream = exec.getInputStream();
stdinOutput = readStringFromInputStream(inputStream);
logger.warn("执行命令'{}'标准流输出:\n{}", command, stdinOutput);
//有些命令就算执行成功也会将输出放到错误流,所以这里也将错误流内容进行读取,便于调试
errorStream = exec.getErrorStream();
errorOutput = readStringFromInputStream(errorStream);
logger.warn("执行命令'{}'错误流输出:\n{}", command, errorOutput);
exitCode = exec.waitFor();
if (exitCode != 0) {
logger.warn("执行命令'{}'可能失败,退出code:{}!", command, exitCode);
} else {
logger.warn("执行命令'{}'成功,退出code:{}!", command, exitCode);
}
} catch (InterruptedException ie) {
logger.error("执行命令'{}'被中断!", command);
} catch (IOException e) {
logger.error("执行命令'" + command + "'抛出异常!", e);
}
return new CommandExecResult(exitCode, stdinOutput, errorOutput);
}
private static String readStringFromInputStream(InputStream stream) throws IOException {
byte[] buff = new byte[128];
int n;
StringBuilder sb = new StringBuilder();
try {
while ((n = stream.read(buff)) != -1) {
sb.append(new String(buff, 0, n));
}
} finally {
stream.close();
}
return sb.toString();
}
@SuppressWarnings("unused")
public static class CommandExecResult {
private int exitCode;
private String stdinOutput;
private String stderrOutput;
public CommandExecResult() {
}
public CommandExecResult(int exitCode, String stdinOutput, String stderrOutput) {
this.exitCode = exitCode;
this.stdinOutput = stdinOutput;
this.stderrOutput = stderrOutput;
}
public int getExitCode() {
return exitCode;
}
public void setExitCode(int exitCode) {
this.exitCode = exitCode;
}
public String getStdinOutput() {
return stdinOutput;
}
public void setStdinOutput(String stdinOutput) {
this.stdinOutput = stdinOutput;
}
public String getStderrOutput() {
return stderrOutput;
}
public void setStderrOutput(String stderrOutput) {
this.stderrOutput = stderrOutput;
}
}
}
该工具类能够很方便的执行命令并获取命令退出码和输出信息。文章来源地址https://www.toymoban.com/news/detail-469428.html
到了这里,关于Java Runtime 类详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!