Java ProcessBuilder教程
原文:http://zetcode.com/java/processbuilder/
Java ProcessBuilder教程显示了如何使用ProcessBuilder创建操作系统进程。
ProcessBuilder
ProcessBuilder用于创建操作系统进程。 其start()方法创建具有以下属性的新Process实例:
- 命令
 - 环境
 - 工作目录
 - 输入来源
 - 标准输出和标准错误输出的目标
 redirectErrorStream
ProcessBuilder运行程序
用command()执行程序。 使用waitFor(),我们可以等待过程完成。
ExecuteProgram.java
package com.zetcode;
import java.io.IOException;
public class ExecuteProgram {
    public static void main(String[] args) throws IOException, InterruptedException {
        var processBuilder = new ProcessBuilder();
        processBuilder.command("notepad.exe");
        var process = processBuilder.start();
        var ret = process.waitFor();
        System.out.printf("Program exited with code: %d", ret);
    }
}
该程序执行 Windows 记事本应用。 它返回其退出代码。
ProcessBuilder命令输出
以下示例执行命令并显示其输出。
ProcessBuilderEx.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ProcessBuilderEx {
    public static void main(String[] args) throws IOException {
        var processBuilder = new ProcessBuilder();
        processBuilder.command("cal", "2019", "-m 2");
        var process = processBuilder.start();
        try (var reader = new BufferedReader(
            new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
该示例运行 Linux cal命令。
processBuilder.command("cal", "2019", "-m 2");
command()执行cal程序。 其他参数是程序的选项。 为了在 Windows 机器上运行命令,我们可以使用以下命令:processBuilder.command("cmd.exe", "/c", "ping -n 3 google.com")。
var process = processBuilder.start();
start()启动了该过程。
try (var reader = new BufferedReader(
    new InputStreamReader(process.getInputStream()))) {
使用getInputStream()方法,我们从流程的标准输出中获取输入流。
February 2019      
Su Mo Tu We Th Fr Sa  
                1  2  
    3  4  5  6  7  8  9  
10 11 12 13 14 15 16  
17 18 19 20 21 22 23  
24 25 26 27 28 
这是输出。
ProcessBuilder重定向输出
使用redirectOutput(),我们可以重定向流程构建器的标准输出目的地。
RedirectOutputEx.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
public class RedirectOutputEx {
    public static void main(String[] args) throws IOException {
        var homeDir = System.getProperty("user.home");
        var processBuilder = new ProcessBuilder();
        processBuilder.command("cmd.exe", "/c", "date /t");
        var fileName = new File(String.format("%s/Documents/tmp/output.txt", homeDir));
        processBuilder.redirectOutput(fileName);
        var process = processBuilder.start();
        try (var reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
该程序将构建器的输出重定向到文件。 它运行 Windows date命令。
processBuilder.redirectOutput(fileName);
我们将流程构建器的标准输出重定向到文件。
try (var reader = new BufferedReader(
    new InputStreamReader(process.getInputStream()))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}
现在输出到文件。
$ echo %cd%
C:\Users\Jano\Documents\tmp
$ more output.txt
Thu 02/14/2019
当前日期已写入output.txt文件。
ProcessBuilder重定向输入和输出
下一个示例同时重定向输入和输出。
src/resources/input.txt
sky
blue
steel
morning
coffee
earth
forest
这是input.txt文件的内容。
ProcessBuilderRedirectIOEx.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
public class ProcessBuilderRedirectIOEx {
    public static void main(String[] args) throws IOException {
        var processBuilder = new ProcessBuilder();
        processBuilder.command("cat")
                .redirectInput(new File("src/resources", "input.txt"))
                .redirectOutput(new File("src/resources/", "output.txt"))
                .start();
    }
}
在程序中,我们将输入从input.txt文件重定向到cat命令,并将命令的输出重定向到output.txt文件。
ProcessBuilder继承 IO
inheritIO()将子流程标准 I/O 的源和目的地设置为与当前 Java 流程相同。
ProcessBuilderInheritIOEx.java
package com.zetcode;
import java.io.IOException;
public class ProcessBuilderInheritIOEx {
    public static void main(String[] args) throws IOException, InterruptedException {
        var processBuilder = new ProcessBuilder();
        processBuilder.command("cmd.exe", "/c", "dir");
        var process = processBuilder.inheritIO().start();
        int exitCode = process.waitFor();
        System.out.printf("Program ended with exitCode %d", exitCode);
    }
}
通过继承已执行命令的 IO,我们可以跳过读取步骤。 程序输出项目目录的内容和显示退出代码的消息。
02/14/2019  04:55 PM    <DIR>          .
02/14/2019  04:55 PM    <DIR>          ..
02/19/2019  01:11 PM    <DIR>          .idea
02/14/2019  04:55 PM    <DIR>          out
02/14/2019  04:52 PM                     433 ProcessBuilderInheritIOEx.iml
02/14/2019  04:53 PM    <DIR>          src
                1 File(s)            433 bytes
                5 Dir(s)  157,350,264,832 bytes free
Program ended with exitCode 0
我们同时获得执行的命令和自己的 Java 程序的输出。
ProcessBuilder环境
environment()方法返回流程构建器环境的字符串映射视图。
ProcessBuilderEnvEx.java
package com.zetcode;
public class ProcessBuilderEnvEx {
    public static void main(String[] args) {
        var pb = new ProcessBuilder();
        var env = pb.environment();
        env.forEach((s, s2) -> {
            System.out.printf("%s %s %n", s, s2);
        });
        System.out.printf("%s %n", env.get("PATH"));
    }
}
该程序显示所有环境变量。
configsetroot C:\WINDOWS\ConfigSetRoot 
USERDOMAIN_ROAMINGPROFILE LAPTOP-OBKOFV9J 
LOCALAPPDATA C:\Users\Jano\AppData\Local 
PROCESSOR_LEVEL 6 
USERDOMAIN LAPTOP-OBKOFV9J 
LOGONSERVER \\LAPTOP-OBKOFV9J 
JAVA_HOME C:\Users\Jano\AppData\Local\Programs\Java\openjdk-11\ 
SESSIONNAME Console 
...
这是 Windows 上的示例输出。
在下一个程序中,我们定义一个自定义环境变量。
ProcessBuilderEnvEx2.java
package com.zetcode;
import java.io.IOException;
public class ProcessBuilderEnvEx2 {
    public static void main(String[] args) throws IOException {
        var pb = new ProcessBuilder();
        var env = pb.environment();
        env.put("mode", "development");
        pb.command("cmd.exe", "/c", "echo", "%mode%");
        pb.inheritIO().start();
    }
}
该程序定义一个mode变量并在 Windows 上输出。
pb.command("cmd.exe", "/c", "echo", "%mode%");
%mode%是 Windows 的环境变量语法; 在 Linux 上,我们使用$mode。
ProcessBuilder目录
directory()方法设置流程构建器的工作目录。
ProcessBuilderDirectoryEx.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
public class ProcessBuilderDirectoryEx {
    public static void main(String[] args) throws IOException {
        var homeDir = System.getProperty("user.home");
        var pb = new ProcessBuilder();
        pb.command("cmd.exe", "/c", "dir");
        pb.directory(new File(homeDir));
        var process = pb.start();
        try (var reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}
该示例将主目录设置为流程生成器的当前目录。 我们显示主目录的内容。
var homeDir = System.getProperty("user.home");
我们得到用户的主目录。
pb.command("cmd.exe", "/c", "dir");
我们定义了一个在 Windows 上执行dir程序的命令。
pb.directory(new File(homeDir));
我们设置流程构建器的目录。
Volume in drive C is Windows
Volume Serial Number is 4415-13BB
Directory of C:\Users\Jano
02/14/2019  11:48 AM    <DIR>          .
02/14/2019  11:48 AM    <DIR>          ..
10/13/2018  08:38 AM    <DIR>          .android
01/31/2019  10:58 PM               281 .bash_history
12/17/2018  03:02 PM    <DIR>          .config
...
这是一个示例输出。
ProcessBuilder非阻塞操作
在下面的示例中,我们创建一个异步过程。
ProcessBuilderNonBlockingEx.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
public class ProcessBuilderNonBlockingEx {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException, TimeoutException, IOException {
        var executor = Executors.newSingleThreadExecutor();
        var processBuilder = new ProcessBuilder();
        processBuilder.command("cmd.exe", "/c", "ping -n 3 google.com");
        try {
            var process = processBuilder.start();
            System.out.println("processing ping command ...");
            var task = new ProcessTask(process.getInputStream());
            Future<List<String>> future = executor.submit(task);
            // non-blocking, doing other tasks
            System.out.println("doing task1 ...");
            System.out.println("doing task2 ...");
            var results = future.get(5, TimeUnit.SECONDS);
            for (String res : results) {
                System.out.println(res);
            }
        } finally {
            executor.shutdown();
        }
    }
    private static class ProcessTask implements Callable<List<String>> {
        private InputStream inputStream;
        public ProcessTask(InputStream inputStream) {
            this.inputStream = inputStream;
        }
        @Override
        public List<String> call() {
            return new BufferedReader(new InputStreamReader(inputStream))
                    .lines()
                    .collect(Collectors.toList());
        }
    }
}
该程序创建一个在控制台上运行ping命令的进程。 它在Executors.newSingleThreadExecutor()方法的帮助下在单独的线程中执行。
processing ping command ...
doing task1 ...
doing task2 ...
Pinging google.com [2a00:1450:4001:825::200e] with 32 bytes of data:
Reply from 2a00:1450:4001:825::200e: time=108ms 
Reply from 2a00:1450:4001:825::200e: time=111ms 
Reply from 2a00:1450:4001:825::200e: time=112ms 
Ping statistics for 2a00:1450:4001:825::200e:
    Packets: Sent = 3, Received = 3, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 108ms, Maximum = 112ms, Average = 110ms
这是输出。
ProcessBuilder管道操作
管道是一种用于将信息从一个程序进程传递到另一个程序进程的技术。
ProcessBuilderPipeEx.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
public class ProcessBuilderPipeEx {
    public static void main(String[] args) throws IOException {
        var homeDir = System.getProperty("user.home");
        var processBuilder = new ProcessBuilder();
        processBuilder.command("cmd.exe", "/c", "dir | grep [dD]o");
        processBuilder.directory(new File(homeDir));
        processBuilder.inheritIO().start();
    }
}
该示例通过管道(|)将信息从dir命令发送到grep命令。
Volume in drive C is Windows
11/14/2018  06:57 PM    <DIR>          .dotnet
02/18/2019  10:54 PM    <DIR>          Documents
02/17/2019  01:11 AM    <DIR>          Downloads
这是输出。
在本教程中,我们使用 Java 的ProcessBuilder执行 OS 进程。 您可能也对相关教程感兴趣: Java Files.walk教程和 Java 教程。
