使用Process执行外部命令

1. Runtime

使用Runtime方法执行外部命令

// 获取当前Runtime
public static Runtime getRuntime();

// 执行命令,如"ping baidu.com"
public Process exec(String command) throws IOException;

// 将命令拆成数组形式,如{"ping", "baidu.com"}
public Process exec(String cmdarray[]) throws IOException;

// 执行命令,指定环境变量(name=value形式)
public Process exec(String command, String[] envp) throws IOException;

// 执行命令,指定环境变量、工作目录(默认继承当前进程的工作目录)
public Process exec(String command, String[] envp, File dir) throws IOException;

2. ProcessBuilder

也是使用ProcessBuilder执行

// 指定命令
public ProcessBuilder command(String... command);

// 不能指定环境变量,只能通过Runtime.exec
ProcessBuilder environment(String[] envp);

// 指定工作目录
public ProcessBuilder directory(File directory);

// 创建子进程执行
public Process start() throws IOException;

3. Process

进程接口。Runtime.exec()方法创建子进程,通过返回的Process实例获取执行情况。

3.1 输入输出

子进程没有自己的终端和控制台。它的所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。
因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流出现失败,则可能导致子进程阻塞,甚至产生死锁。

// 向子进程提供stdin
public abstract OutputStream getOutputStream();
// 读取子进程stdout
public abstract InputStream getInputStream();
// 读取子进程stderr
public abstract InputStream getErrorStream();

也可以使用ProcessBuilder重定向子进程的标准IO。

// 重定向stdin
public ProcessBuilder redirectInput(File file);
// 重定向stdout
public ProcessBuilder redirectOutput(File file);
// 重定向stderr
public ProcessBuilder redirectError(File file);

3.2 执行状态

// 阻塞等待子进程执行完毕
public abstract int waitFor() throws InterruptedException;

// 超时等待子进程执行完毕
public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException;

// 非阻塞获取子进程退出值,0表示正常结束
// 若子进程并没有终止将抛出IllegalThreadStateException
public abstract int exitValue();

// 非阻塞检查子进程存活状态
public boolean isAlive();

3.3 终止进程

public abstract void destroy();

public Process destroyForcibly();

4. 示例

4.1 执行ping

// 指定命令
Process process = Runtime.getRuntime().exec("ping baidu.com");
// 阻塞等待
process.waitFor();
// 打印退出值
System.out.println(process.exitValue());

4.2 获取进程执行输出

先定义一个通用输入流处理函数:

private static final Consumer consumer = (InputStream inputStream) -> {
    BufferedReader bufferedReader = null;
    try {
        bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "gbk"));
        String line = null;
        while ((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
};

改造程序:

Process process = Runtime.getRuntime().exec("ping baidu.com");
consumer.accept(process.getInputStream());
process.waitFor();
System.out.println(process.exitValue());

结果如图:


image.png

4.3 并发读取输出流和错误流

由于输出流和错误流缓冲区空间有限,如果不及时消费,会导致写操作阻塞,进程夯住。

hang.sh:写stdout和stderr

#!/bin/bash

for i in {1..1000}
do
    echo "stdout hang................" >&1
done

for i in {1..1000}
do
    echo "stderr hang................" >&2
done

对于stdout和stderr都有写入的情况,如果串行读取或只读取一个,会导致进程夯住。

Process process = Runtime.getRuntime().exec("bash D:\\Temp\\Process\\hang.sh");
consumer.accept(process.getInputStream());
consumer.accept(process.getErrorStream());
process.waitFor();
System.out.println(process.exitValue());

需要对流并发读取:

Process process = Runtime.getRuntime().exec("bash D:\\Temp\\Process\\hang.sh");
new Thread(() -> consumer.accept(process.getInputStream())).start();
new Thread(() -> consumer.accept(process.getErrorStream())).start();
process.waitFor();
System.out.println(process.exitValue());

4.4 重定向标准流

ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.directory(new File("D:\\Temp\\Process")); // 设置工作目录
processBuilder.command("bash", "redirect.sh"); // 必须分开写
processBuilder.redirectInput(new File("D:\\Temp\\Process\\stdin.log")); // 重定向stdin
processBuilder.redirectOutput(new File("D:\\Temp\\Process\\stdout.log")); // 重定向stdout
processBuilder.redirectError(new File("D:\\Temp\\Process\\stderr.log")); // 重定向stderr
Process process = processBuilder.start();
process.waitFor();

脚本如下:从stdin分别读取5行,写入stdout和stderr。

#!/bin/bash

for i in {1..5}
do
    read line
    echo -e "$line\n" >&1
done

for i in {1..5}
do
    read line
    echo -e "$line\n" >&2
done

你可能感兴趣的:(使用Process执行外部命令)