Java调用外部可执行程序或系统命令
文章目录
一)、简述
Java调用外部可执行程序或系统命令,主要有以下两种调用方式
1、Runtime.getRuntime().exec
2、new ProcessBuilder().start()
二)、Runtime.getRuntime().exec
1、概述
Runtime.getRuntime().exec 用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。功能和windows“运行”类似
2、调用方式
Process process = Runtime.getRuntime().exec("cmd");
process.waitfor();
第一行的“cmd”是要执行的命令,Runtime.getRuntime() 返回当前应用程序的Runtime对象,该对象的 exec() 方法指示Java虚拟机创建一个子进程执行指定的可执行程序,并返回与该子进程对应的Process对象实例。通过Process可以控制该子进程的执行或获取该子进程的信息。
第二条语句的目的等待子进程完成再往下执行。
备注: java.lang.Process.waitFor()方法将导致当前的线程等待,如果必要的话,直到由该Process对象表示的进程已经终止。此方法将立即返回,如果子进程已经终止。如果子进程尚未终止,则调用线程将被阻塞,直到子进程退出。
3、方法API
Runtime.getRuntime().exec共有六个重载方法:
// 在单独的进程中执行指定的外部可执行程序的启动路径或字符串命令
public Process exec(String command)
// 在单独的进程中执行指定命令和变量
public Process exec(String[] cmdArray)
// 在指定环境的独立进程中执行指定命令和变量
public Process exec(String command, String[] envp)
// 在指定环境的独立进程中执行指定的命令和变量
public Process exec(String[] cmdArray, String[] envp)
// 在指定环境和工作目录的独立进程中执行指定的字符串命令
public Process exec(String command, String[] envp, File dir)
// 在指定环境和工作目录的独立进程中执行指定的命令和变量
public Process exec(String[] cmdarray, String[] envp, File dir)
-------------------------------------------------------------------------------------
// 参数说明:
cmdarray // 包含所调用命令及其参数的数组。数组第一个元素是命令,其余是参数
envp // 字符串数组,其中每个元素的环境变量的设置格式为 name=value,如果子进程应该继承当前进程的环境,则该参数为null
dir // 子进程的工作目录;如果子进程应该继承当前进程的工作目录,则该参数为null
// 参数cmdArray 示例:shutdown -s -t 3600
String arr[] = {"shutdown","-s","-t","3600"};
Process process = Runtime.getRuntime().exec(arr[]);
/*
注意:
在调用这个方法时,不能将命令和参数放在一起,eg:String arr[] = {"shutdown -s -t 3600"};
这样会导致程序把“shutdown -s -t 3600”当成是一条命令的名称,然后去查找“shutdown -s -t 3600”这条命令,它当然会找不到,所以就会报错
*/
备注:
Runtime.exec() 不是cmd或shell环境,因此无法直接调用dir等命令,需要在程序中读取运行的操作系统平台,以调用不同的命令解释器(NT:cmd.exe,windows 95/98:command.exe,linux:/bin/sh)
Procss类将持有该程序返回 Java VM 的引用。这个procss类是一个抽象类,具体子类的实现依赖于不同的底层操作系统。
4、Process 的常用方法
// 导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。
int waitFor()
/* 如果已终止该子进程,此方法立即返回。
如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程,0 表示正常终止 */
// 杀掉子进程
void destroy()
// 返回子进程的出口值,值 0 表示正常终止
int exitValue()
// 获取子进程的错误流
InputStream getErrorStream()
// 获取子进程的输入流
InputStream getInputStream()
// 获取子进程的输出流
OutputStream getOutputStream()
5、程序阻塞问题
通过 Process实例.getInputStream() 和 Process实例.getErrorStream() 获取的输入流和错误信息流是缓冲池向当前Java程序提供的,而不是直接获取外部程序的标准输出流和标准错误流。
而缓冲池的容量是一定的,因此若外部程序在运行过程中不断向缓冲池输出内容,当缓冲池填满,那么外部程序将暂停运行直到缓冲池有空位可接收外部程序的输出内容为止。(采用xcopy命令复制大量文件时将会出现该问题)
解决办法:当前的Java程序不断读取缓冲池的内容,从而为腾出缓冲池的空间。
Runtime r = Runtime.getRuntime();
try {
Process proc = r.exec("cmd /c CMD"); // 假设该操作为造成大量内容输出
// 采用字符流读取缓冲池内容,腾出空间
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream(), "GBK")));
String line = null;
while ((line = reader.readLine()) != null){
System.out.println(line);
}
/* 或采用字节流读取缓冲池内容,腾出空间
ByteArrayOutputStream pool = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count = -1;
while ((count = proc.getInputStream().read(buffer)) != -1){
pool.write(buffer, 0, count);
buffer = new byte[1024];
}
System.out.println(pool.toString("gbk"));
*/
int exitVal = proc.waitFor();
System.out.println(exitVal == 0 ? "成功" : "失败");
} catch(Exception e) {
e.printStackTrace();
}
备注: 外部程序在执行结束后需自动关闭,否则不管是字符流还是字节流均由于既读不到数据,又读不到流结束符,从而出现阻塞Java进程运行的情况。cmd的参数 “/c” 表示当命令执行完成后关闭自身。
6、不同系统执行系统命令的问题
Windows:cmd
Linux:/bin/sh
7、简单操作代码使用
备注: 需要将Stream关闭,此处代码未关闭
使用方式可参考以上代码,如6所示 ↑↑↑↑↑↑↑↑↑
三)、new ProcessBuilder().start()
1、概述
通过Java执行系统命令,与cmd中或者终端上一样执行shell命令,最典型的用法就是使用Runtime.getRuntime().exec(command)或者new ProcessBuilder(cmdArray).start()。
ProcessBuilder类是J2SE 1.5在java.lang中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。在J2SE 1.5之前,都是由Process类处理实现进程的控制管理。每个ProcessBuilder实例管理一个进程属性集合。start()方法使用这些属性创建一个新的流程实例。可以从同一个实例多次调用start()方法,以创建具有相同或相关属性的新子进程。
ProcessBuilder的构造方法接收一个命令参数的数组形式,其中,第一个元素代表要执行的系统命令,后面的元素代表要传给该命令的参数。
2、调用方式
List<String> cmd = new ArrayList<>();
cmd.add("python");
cmd.add("F:/A_javafile/python.py");
ProcessBuilder pb = new ProcessBuilder(cmd);
// 合并 错误流和标准流
pb.redirectErrorStream(true);
Process process = pb.start();
“cmd”是要执行的命令和参数
3、方法API
构造方法摘要
ProcessBuilder(List<String> command)
利用指定的操作系统程序和参数构造一个进程生成器。
ProcessBuilder(String… command)
利用指定的操作系统程序和参数构造一个进程生成器。
方法摘要
command()
返回此进程生成器的操作系统程序和参数。设置此过程构建器的操作系统程序和参数。此方法不会复制命令列表。该列表的后续更新将反映在流程构建器的状态中。不检查命令是否对应于有效的操作系统命令。
command(List<String> command)
设置此进程生成器的操作系统程序和参数。
command(String… command)
设置此进程生成器的操作系统程序和参数。
directory()
返回此进程生成器的工作目录。
directory(File directory)
设置此进程生成器的工作目录。
environment()
返回此进程生成器环境的字符串映射视图。 environment方法获得运行进程的环境变量,得到一个Map,可以修改环境变量
redirectErrorStream()
通知进程生成器是否合并标准错误和标准输出。如果该属性为真,则由该对象的start()方法启动的子进程生成的任何错误输出都将与标准输出合并,以便两者都可以使用Process.getInputStream()方法读取。这使得将错误消息与相应的输出关联起来更容易,而从 Process.getErrorStream() 返回的流读取将直接到达文件尾。初始值为false。
redirectErrorStream(boolean redirectErrorStream)
设置此进程生成器的 redirectErrorStream 属性。
start() 使用此进程生成器的属性启动一个新进程。
4、简单操作代码使用
public class ProcessBuilderTest {
public static void main(String[] args) {
List<String> params = new ArrayList<String>();
params.add("java");
params.add("-jar");
params.add("ProcessJar.jar");
params.add("args1");
params.add("args2");
params.add("args3");
ProcessBuilder processBuilder = new ProcessBuilder(params);
// System.out.println(processBuilder.directory());
// System.out.println(processBuilder.environment());
processBuilder.redirectErrorStream(true);
try {
Process process = processBuilder.start();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
int exitCode = process.waitFor();
System.out.println("exitCode = "+exitCode);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
四)、不同系统如何处理命令
ProcessBuilder builder;
String charSet;
String os = System.getProperty("os.name");
if (os.toLowerCase().contains("win")) {
builder = new ProcessBuilder("cmd", "/c", exportCmd.toString());
charSet = "gbk";
} else {
builder = new ProcessBuilder("sh", "-c", exportCmd.toString());
charSet = "utf-8";
}