首页 > 编程语言 >Java中使用ProcessBuilder执行命令

Java中使用ProcessBuilder执行命令

时间:2023-09-03 19:31:47浏览次数:52  
标签:执行命令 Java ProcessBuilder processBuilder process Process 进程 String

在Java中我们可以使用ProcessBuilder来创建一个进程并执行命令。在使用前我们首先了了解下两个程序。


一、认识命令行解释程序

不论在windows中还是linux中都可以通过命令行方式来执行某些程序或脚本,而解析命令行的程序被称为shell,其本身是一种命令行解释器,用于与操作系统进行交互和执行命令。

我们在java中执行某些命令的时也是借助操作系统的shell程序来完成的,但Windows系统和Linux系统中的shell是有区别的,我们先看看两类系统中的shell应该如何被使用。


1、Windows

在windows中可通过cmd.exe来启动一个新的命令解释器实例,其格式为

CMD [/A | /U] [/Q] [/D] [/E:ON | /E:OFF] [/F:ON | /F:OFF] [/V:ON | /V:OFF]
    [[/S] [/C | /K] string]

CMD可单独执行,例如我们在widows系统中通过cmd命令打开命令行窗口进行交互,也可以携带一些参数和指令直接执行。

关于CMD的更多说明可在windows系统中通过 help cmd 命令查看

与在Java中执行命令相关的命令格式为

CMD  [[/S] [/C | /K] string]
  • /C      执行字符串指定的命令然后终止
  • /K      执行字符串指定的命令但保留
  • /S      修改 /C 或 /K 之后的字符串处理

如果指定了 /C 或 /K,则会将其之后的剩余部分作为一个命令行处理,其中,会使用下列逻辑处理可能存在的引号(")字符:

  1. 如果符合下列所有条件,则会保留命令行上的引号字符:
  • 不带/S开关
  • 正好两个引号字符
  • 在两个引号字符之间无任何特殊字符.特殊字符:&<>()@^|
  • 在两个引号字符之间至少有一个空格字符
  • 在两个引号字符之间的字符串是某个可执行文件的名称。
  1. 否则,老办法是看第一个字符是否是引号字符,如果是,则去掉首字符并删除命令行上最后一个引号,保留最后一个引号之后的所有文本。


2、Linux

在Linux中可以通过以下的方式来执行命令行文件或命令

sh <command_file> [argument...]
sh -c <command_string> [command_name [argument...]]

command_file:包含命令的文件的路径名。如果路径名包含一个或多个<斜杠>字符,则实现尝试读取该文件;该文件不必是可执行的。如果路径名不包含<斜杠>字符:

argument:位置参数($1,$2,等等)如果有,应该设置为实参。

command_string:由shell解释为一个或多个命令的字符串。如果command_string操作数为空字符串,sh将以零退出状态退出。

command_name:在执行command_string中的命令时分配给特殊参数0的字符串。如果未指定command_name,则应将特殊参数0设置为从父程序传递给sh的第一个参数的值(例如,C程序的argv[0]),该参数通常是用于执行sh实用程序的路径名。



二、使用ProcessBuilder


1、ProcessBuilder介绍

ProcessBuilder类用于创建操作系统进程,每个ProcessBuilder实例管理一个Process属性集合,通过start()方法用这些属性创建一个新的Process实例。

start()方法可以从同一实例中重复调用,以创建具有相同或相关属性的新子流程。

每个ProcessBuilder管理这些流程属性:

1) 一个字符串列表形式的命令(command),表示要调用的外部程序文件及其参数(如果有的话).哪个字符串列表表示有效的操作系统命令取决于系统

2) 一个environment,它是从变量到值的依赖于系统的映射。初始值是当前进程环境的副本(参见System.getenv())。

3) 工作目录(working directory),默认值是当前进程的当前工作目录,通常是由系统属性user.dir命名的目录。

4) 标准输入的来源。默认情况下,子进程从管道读取输入。Java代码可以通过Process.getOutputStream()返回的输出流访问该管道。 但是,可以使用redirectInput将标准输入重定向到另一个源。在这种情况下,Process.getOutputStream()将返回一个空输出流,其中:

  • 写方法总是抛出IOException
  • close方法什么也不做

5)标准输出和标准错误的目标。默认情况下,子流程将标准输出和标准错误写入管道。Java代码可以通过Process.getInputStream()和Process.getErrorStream()返回的输入流访问这些管道。但是,可以使用redirectOutput和redirectError将标准输出和标准错误重定向到其他目的地。在这种情况下,Process.getInputStream()和/或Process.getErrorStream()将返回一个空输入流,其中:

  • read方法总是返回-1
  • 可用方法总是返回0
  • close方法什么也不做

6)redirectErrorStream属性。最初该属性为false,这意味着子流程的标准输出和错误输出被发送到两个独立的流,可以使用Process.getInputStream()和Process.getErrorStream()方法访问这两个流。如果该值被设置为true,则:

  • 标准错误与标准输出合并,并始终发送到相同的目的地(这使得将错误消息与相应的输出关联起来更容易)
  • 标准错误和标准输出的共同目标可以使用redirectOutput重定向
  • 在创建子进程时,redirectError方法设置的任何重定向都将被忽略
  • Process.getErrorStream()返回的流将始终是一个空输入流

修改进ProcessBuilder的属性将影响随后由该对象的start()方法启动的进程,但不会影响先前启动的进程或Java进程本身。 大多数错误检查是由start()方法执行的。可以修改对象的状态,使start()失败。例如,将command属性设置为空列表不会抛出异常,除非调用start()。

注意,这个类不是同步的。如果多个线程并发访问ProcessBuilder实例,并且至少有一个线程在结构上修改了其中一个属性,则必须在外部同步。


2、常用方法介绍

public void methods(File workingDirectory,List<String> commands){
        ProcessBuilder processBuilder = new ProcessBuilder() ;
        /*
         * 使用指定的操作系统程序和参数构造进程构建器。
         * 此构造函数不复制命令列表。对列表的后续更新将反映在流程构建器的状态中。
         * 不检查command是否对应有效的操作系统命令。
         * */
        //new ProcessBuilder(List<String> command) ;
        /*
         * 使用指定的操作系统程序和参数构造进程构建器。
         * 这是一个方便的构造函数,它将流程构建器的命令设置为一个字符串列表,其中包含与命令数组相同的字符串,顺序相同。
         * 不检查command是否对应有效的操作系统命令。
         * */
        //new ProcessBuilder(String... command) ;

        //设置此ProcessBuilder的操作系统程序和参数。如果构造函数没有设置命令可通过方法设置
        processBuilder.command(commands) ;
        //返回此进程构建器的操作系统程序和参数。
        List<String> commandList = processBuilder.command() ;


        //设置工作目录.默认是当前进程的System.getProperty("user.dir")值
        processBuilder.directory(workingDirectory) ;

        //返回此ProcessBuilder环境的字符串映射视图
        Map<String,String> environment = processBuilder.environment() ;
        //添加环境变量(如果有必要)
        environment.put("EVN_NAME","") ;

        /*
         * 将子进程标准I/O的源和目标设置为与当前Java进程的源和目标相同。
         * 这是一个便捷方法,等同调用了下面三个方法
         * redirectInput(Redirect.INHERIT)
         * redirectOutput(Redirect.INHERIT)
         * redirectError(Redirect.INHERIT)
         * */
        processBuilder.inheritIO() ;


        /*
         * 设置此进程构建器的redirectErrorStream属性
         * 如果此属性为true,则随后由该对象的start()方法启动的子进程生成的任何错误输出将与标准输出合并,
         * 以便可以使用Process.getInputStream()方法读取两者。
         * 这使得将错误消息与相应的输出相关联变得更加容易。初始值为false。
         * */
        processBuilder.redirectErrorStream(true) ;
        /*
         * 说明此ProcessBuilder是否合并标准错误和标准输出
         * */
        boolean flag = processBuilder.redirectErrorStream() ;


        //将此流程构建器的标准输入源设置为文件
        processBuilder.redirectInput(new File("")) ;
        //设置此流程构建器的标准输入源
        processBuilder.redirectInput(ProcessBuilder.Redirect.to(new File("log.txt"))) ;
        //返回此流程构建器的标准输入源。
        ProcessBuilder.Redirect redirectInput = processBuilder.redirectInput() ;


        //将此流程构建器的标准输出目的地设置为文件。
        processBuilder.redirectOutput(new File("")) ;
        //设置此流程构建器的标准输出目的地。
        processBuilder.redirectOutput(ProcessBuilder.Redirect.from(new File("command.txt"))) ;
        //返回此流程构建器的标准输出目的地。
        ProcessBuilder.Redirect redirectOutput = processBuilder.redirectOutput() ;


        //将此进程构建器的标准错误目标设置为文件
        processBuilder.redirectError(new File("")) ;
        //设置此流程构建器的标准错误目的地。
        processBuilder.redirectError( ProcessBuilder.Redirect.PIPE) ;
        //返回此流程构建器的标准错误目的地。
        ProcessBuilder.Redirect redirectError = processBuilder.redirectError() ;


        /*
         * 使用此流程构建器的属性启动新流程。
         * Process可用于控制该进程并获取有关该进程的信息,其提供了执行进程输入、执行进程输出、等待进程完成、检查进程退出状态以及销毁(杀死)进程的方法
         *
         * 创建进程的方法可能不适用于某些本机平台上的特殊进程,例如本机窗口进程、守护进程、Windows上的Win16/DOS进程或shell脚本
         * 默认情况下,创建的子进程没有自己的终端或控制台。它的所有标准I/O(即stdin, stdout, stderr)操作将被重定向到父进程,
         * 在那里它们可以通过使用getOutputStream(), getInputStream()和getErrorStream()方法获得的流来访问。
         * 父进程使用这些流向子进程提供输入并从子进程获取输出。由于某些本机平台仅为标准输入和输出流提供有限的缓冲区大小,因此不能及时写入输入流或读取子进程的输出流可能会导致子进程阻塞,甚至死锁。
         * 如果需要,还可以使用ProcessBuilder类的方法重定向子进程I/O。
         *
         * 当不再有对Process对象的引用时,子进程不会被终止,而是继续异步执行。
         * 由process对象表示的进程并不要求相对于拥有process对象的Java进程异步或并发地执行。
         *
         * 此方法检查该命令是否为有效的操作系统命令。
         * 哪些命令有效取决于系统,但至少命令必须是由非空字符串组成的非空列表。
         * 在执行该方法的过程中,可能有以下情况导致出现错误
         * 1)找不到操作系统程序文件
         * 2)程序文件的访问被拒绝了
         * 3)工作目录不存在
         * */
        Process process = processBuilder.start() ;

        /*
         * 返回连接到子进程的正常输出的输入流。流通过管道从这个process对象表示的流程的标准输出中获取数据。
         * 如果子进程的标准输出已使用ProcessBuilder重定向。redirectOutput则此方法将返回一个空输入流。
         * 否则,如果使用ProcessBuilder重定向了子进程的标准错误(redirectErrorStream)则此方法返回的输入流将接收合并的标准输出和子进程的标准错误。
         * */
        InputStream inputStream = process.getInputStream() ;

        /*
         * 返回连接到子进程的正常输入的输出流。流的输出通过管道连接到由此process对象表示的流程的标准输入。
         * 如果子进程的标准输入已使用ProcessBuilder重定向。redirectInput则此方法将返回空输出流。
         * */
        OutputStream outputStream = process.getOutputStream() ;

        /*
         * 返回连接到子进程的错误输出的输入流。流通过管道从process对象表示的流程的错误输出中获取数据。
         * 如果子进程的标准错误已使用ProcessBuilder重定向(ProcessBuilder#redirectError(Redirect)、ProcessBuilder#redirectErrorStream(boolean))
         * 那么这个方法将返回一个空输入流。
         */
        InputStream errorStream = process.getErrorStream() ;


        //测试由此进程表示的子进程是否处于活动状态。
        boolean isAlive = process.isAlive() ;
        /*
         * 返回此Process对象表示的子进程的退出值。按照惯例,0表示正常终止。
         * */
        int exitCode = process.exitValue() ;

        /*
         * 如果需要,使当前线程等待,直到由此process对象表示的进程终止
         * 返回此Process对象表示的子进程的退出值。按照惯例,0表示正常终止。
         */
        exitCode = process.waitFor() ;

        //使当前线程等待(如有必要),直到由此Process对象表示的子进程终止或指定的等待时间结束。
        boolean isSuccess = process.waitFor(1, TimeUnit.MINUTES);

        //杀死子进程。此Process对象表示的子进程是否被强制终止取决于实现。
        process.destroy();

        /*
         * 杀死子进程。此Process对象表示的子进程被强制终止。
         * 此方法的默认实现调用destroy(),因此可能不会强制终止进程。强烈建议该类的具体实现使用兼容的实现覆盖此方法。
         * 在ProcessBuilder.start()和Runtime.exec(java.lang.String)返回的Process对象上调用此方法将强制终止进程。
         * 注意:子进程可能不会立即终止。也就是说,isAlive()可能会在destroy强行()被调用后的一小段时间内返回true。如果需要,这个方法可以链接到waitFor()
         * */
        process.destroyForcibly() ;
    }


3、样例代码

package org.example;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Description ShellUtil
 * @Date 2023/9/3 10:38
 * @Version V1.0
 **/
public class ShellUtil {

    //其实已经被/bin/bash所替代
    protected static final String SH_SHELL = "/bin/sh";
    protected static final String CMD_SHELL = "cmd.exe";


    public static class Result{
        //命令执行是否成功
        public boolean success ;
        //执行的信息
        private String message ;

        public Result(boolean success){
            this(success,null) ;
        }
        public Result(boolean success,String message){
            this.success = success ;
            this.message = message ;
        }

        public boolean isSuccess() {
            return success;
        }

        public String getMessage() {
            return message;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder() ;
            sb.append("isSuccess:") ;
            sb.append(success) ;
            sb.append(System.lineSeparator()) ;
            sb.append(System.lineSeparator()) ;
            sb.append(message) ;

            return sb.toString() ;
        }
    }

    private static List<String> prefixCommands(){
        boolean isWindows = System.getProperty("os.name")
                .toLowerCase().startsWith("windows");
        List<String> commands = new ArrayList<>() ;
        if (isWindows) {
            commands.add(CMD_SHELL) ;
            commands.add("/c") ;
        }else{
            commands.add(SH_SHELL) ;
            commands.add("-c") ;
        }
        return commands ;
    }

    /**
     * @param commands  要执行的命令行
     * @throws Exception
     */
    public static Result exec(List<String> commands) throws Exception {
        return exec(null,commands);
    }

    /**
     * @param workingDirectory 命令执行时候的工种目录
     * @param execCommands  要执行的命令行
     * @throws Exception
     */
    public static Result exec(File workingDirectory,List<String> execCommands) throws Exception {
        List<String> commands = prefixCommands() ;
        commands.addAll(execCommands) ;
        ProcessBuilder processBuilder = new ProcessBuilder(commands) ;
        if(workingDirectory != null){
            processBuilder.directory(workingDirectory) ;
        }

        //将子进程标准I/O的源和目标设置为与当前Java进程的源和目标相同
        //processBuilder.inheritIO() ;

        //设置合并标准错误和标准输出
        //processBuilder.redirectErrorStream(true) ;

        boolean isSuccess = false ;
        StringBuilder msgBuilder = new StringBuilder() ;

        Process process = null ;
        try{
            //启动新进程并执行命令
            process = processBuilder.start() ;

            //返回连接到子进程的正常输出的输入流
            try(InputStream inputStream = process.getInputStream()){
                String message = processMessage(inputStream,StandardCharsets.UTF_8.name());
                msgBuilder.append(message) ;
            }

            //返回连接到子进程的错误输出的输入流
            try(InputStream errorStream = process.getErrorStream()){
                String errorMsg = processMessage(errorStream,osEncode()) ;
                msgBuilder.append(System.lineSeparator()) ;
                msgBuilder.append(System.lineSeparator()) ;
                msgBuilder.append(errorMsg) ;
            }

            //使当前线程等待,直到子进程终止或指定的等待时间结束
            isSuccess = process.waitFor(30, TimeUnit.MINUTES);
        }finally {
            if(process != null){
                //销毁进程
                process.destroy();
            }
        }
        return new Result(isSuccess,msgBuilder.toString()) ;
    }

    private static String processMessage(InputStream inputStream,String charsetName) throws Exception {
        if(charsetName == null){
            //JVM默认的字符编码
            charsetName = Charset.defaultCharset().name() ;
        }
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName) ;
        StringBuilder sb = new StringBuilder() ;
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader) ;
        String line = null ;
        //重要:这里将一直阻塞,直到输入数据可用、检测到流的结尾或抛出异常
        //也就是说,如果inputStream中没有可用的数据(例如命令执行时的错误信息)则程序就一直阻塞了
        while((line = bufferedReader.readLine()) != null){
            sb.append(line) ;
            sb.append(System.lineSeparator()) ;
        }
        return sb.toString() ;
    }

    /**
     * 操作系统编码
     */
    private static String osEncode(){
        String osEncode = (String)System.getProperties().get("sun.jnu.encoding");
        System.out.println(osEncode);
        return osEncode ;
    }

    /**
     * 文件系统编码
     */
    private static String fileEncode(){
        String fileEncode=(String)System.getProperties().get("file.encoding");
        System.out.println(fileEncode);
        return fileEncode ;
    }

    public static void main(String[] args) throws Exception {
        //方式1
        List<String> commandList = new ArrayList<>() ;
        commandList.add("mvn") ;
        commandList.add("compile") ;
        Result result = ShellUtil.exec(commandList);
        System.out.println(result);

        //方式2
        List<String> commandList2 = new ArrayList<>() ;
        String mvnPath = "\"D:/Program Files/maven352/bin/mvn\"" ;
        commandList2.add(mvnPath) ;
        commandList2.add("compile") ;
        Result result2 = ShellUtil.exec(commandList2);
        System.out.println(result2);

        //方式3
        List<String> commandList3 = new ArrayList<>() ;
        String mvnPath2 = "\"D:/Program Files/maven352/bin/mvn\" compile" ;
        commandList3.add(mvnPath2) ;
        Result result3 = ShellUtil.exec(commandList);
        System.out.println(result3);
    }
}




标签:执行命令,Java,ProcessBuilder,processBuilder,process,Process,进程,String
From: https://blog.51cto.com/dengshuangfu/7342760

相关文章

  • Java对象从创建到回收全过程
    先了解下Java虚拟机内存组成,当Java虚拟机启动后,会将系统分配给JVM的空间逻辑上划分为堆、虚拟机栈、本地方法栈、方法区、程序计数器五个部分,如下图所示:堆:放置new出来的对象、数组虚拟机栈:线程运行前,会给其分配一个线程栈空间,线程中每个方法执行都会生成一个栈帧放入线程栈中,栈帧里......
  • Java内存模型(JMM)详解
    面试官:讲讲什么是JMMJMM(Java内存模型)详解1什么是JMM?JMM就是Java内存模型(JavaMemoryModel)JMM是一个抽象的概念,他描述的是和多线程相关的一组规范,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范,更方便的开发多线程程序。这样一来,即使同一个程序在不同的虚拟......
  • 无涯教程-JavaScript - TINV函数
    TINV函数取代了Excel2010中的T.INV.2T函数。描述该函数返回学生t分布的两尾逆。语法TINV(probability,deg_freedom)争论Argument描述Required/OptionalProbabilityTheprobabilityassociatedwiththetwo-tailedStudent'st-distribution.RequiredDeg_freedom......
  • java基础-方法method-day05
    1.语法[修饰符]返回值类型方法名(形参列表){逻辑主体}publicclassTestMethod01{ publicstaticintadd(intnum1,intnum2){ intsum=0; sum+=num1; sum+=num2; returnsum; } publicstaticvoidmain(String[]args){ intsum1=add(10......
  • JavaGuide基础3
    异常Exception和ErrorException:程序本身可以处理的异常,可以通过catch来进行捕获。Exception又可以分为CheckedException(受检查异常,必须处理)和UncheckedException(不受检查异常,可以不处理)。Error:Error属于程序无法处理的错误,不建议通过catch捕获。例如Jav......
  • JavaScript – 小技巧 Tips
    1e6等价于1+后面6个零console.log(1e6===1_000_000); 模拟C#的Record DeconstructclassSizeimplementsIterable<number>{constructor(publicwidth:number,publicheight:number){}*[Symbol.iterator](){......
  • IDEA2022.3.1创建JavaWeb项目步骤
    IDEA2022与2021相比,更新后创建新项目时少了JavaWeb项目选项,关于2022版创建JavaWeb项目步骤如下:创建maven项目,填写好后直接点击create即可,项目名称可根据自身情况自己命名。 2.在pom.xml肿设置打包方式为war包。3.补齐MavenWeb项目缺失的webapp目录结构  4......
  • Java正整数除法向上取整
    1、简介在今天刷每日一题的时候看到的,感觉和以前自己写的向上取证的写法比起来好很多,在此记录。来源:1921.消灭怪物的最大数量-力扣(LeetCode)2、内容仅仅在正整数除法,三种都可用1、Math.ceil()2、x/y+(x%y==0?0:1)3、(x-1)/y+1classSolution{publicstaticvoidma......
  • Java:SpringBoot实现定时任务Scheduled
    代码示例packagecom.example.demo.config;importorg.springframework.context.annotation.Configuration;importorg.springframework.scheduling.annotation.EnableScheduling;importorg.springframework.scheduling.annotation.Scheduled;importjava.text.SimpleDat......
  • 无涯教程-JavaScript - STDEV函数
    STDEV函数替代Excel2010中的STDEV.S函数。描述该函数根据样本估算标准偏差。标准偏差是对值与平均值(平均值)的分散程度的度量。语法STDEV(number1,[number2],...)争论Argument描述Required/OptionalNumber1Thefirstnumberargumentcorrespondingtoasampleo......