首页 > 系统相关 >openjdk源码-java是如何执行shell命令的

openjdk源码-java是如何执行shell命令的

时间:2023-12-26 19:57:11浏览次数:44  
标签:shell java String cmd cmdarray 源码 new 执行

一般我们在java中调用shell脚本的方式如下

    public int executeLinuxCmd(String cmd) {
        LOGGER.info("cmd:{}", cmd);
        Runtime run = Runtime.getRuntime();
        try {
            Process process = run.exec(cmd);
            InputStream in = process.getInputStream();
            BufferedReader bs = new BufferedReader(new InputStreamReader(in));
            StringBuffer out = new StringBuffer();
            byte[] b = new byte[8192];
            for (int n; (n = in.read(b)) != -1;) {
                out.append(new String(b, 0, n));
            }
            LOGGER.info("job result:{}", out.toString());
            in.close();
            int exitcode = process.waitFor();
            LOGGER.info("exitcode:{}", exitcode);
            process.destroy();
            if(exitcode == 0) {
                return 1;
            }
            return -1;
        } catch (IOException e) {
            LOGGER.error("e",e);
        } catch (InterruptedException e) {
            LOGGER.error("e",e);
        }

        return -1;
    }

下面我们分析一下,其调用过程

1 执行线

首先我们创建了一个RunTime类

 Runtime run = Runtime.getRuntime(); 

这是java.lang下的一个类,每个java进程都会有一个RuntTime实例,其中为我们提供了一些在java进程外执行系统命令的api。

然后,执行如下代码执行这个cmd命令

 Process process = run.exec(cmd); 

这个代码会创建一个新的进程,然后在新进程中执行这个cmd命令

下面的输入流作用是从执行的shell命令的输出中读取数据

 InputStream in = process.getInputStream(); 

然后将输出写到输出流,进而打印出这个输出。

下面的代码作用是等待子进程执行完并获取子进程的退出码。

 int exitcode = process.waitFor(); 

2 run.exec(cmd)做了啥

run.exec(cmd)调用的是RunTime下的方法,代码如下

    public Process exec(String command) throws IOException {
        return exec(command, null, null);
    }

进而调用(我们只需看最后一行)

    public Process exec(String command, String[] envp, File dir)
        throws IOException {
        if (command.length() == 0)
            throw new IllegalArgumentException("Empty command");
        StringTokenizer st = new StringTokenizer(command);// 该类是字符串分割器,将会把xxx.sh aaa bbb分割成数组[xxx.sh,aaa,bbb]
        String[] cmdarray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++)
            cmdarray[i] = st.nextToken();
        return exec(cmdarray, envp, dir);// 第一个参数是数组[xxx.sh,aaa,bbb],后两个参数都是null
    }

进而执行,这里会创建一个ProcessBuilder对象,然后调用其start方法

    public Process exec(String[] cmdarray, String[] envp, File dir)
        throws IOException {
        return new ProcessBuilder(cmdarray)
            .environment(envp)
            .directory(dir)
            .start();
    }

我们看一下start方法

    public Process start() throws IOException {
        String[] cmdarray = command.toArray(new String[command.size()]);
        cmdarray = cmdarray.clone();

        String prog = cmdarray[0];

        String dir = directory == null ? null : directory.toString();

        try {
            return ProcessImpl.start(cmdarray, // 除了cmdarray(为[xxx.sh,aaa,bbb])和redirectErrorStream(为false)其他参数都是null。
                                     environment,
                                     dir,
                                     redirects,
                                     redirectErrorStream);
        } catch (IOException | IllegalArgumentException e) {
            
        }
    }

然后执行ProcessImpl的如下方法

    static Process start(String cmdarray[],
                         java.util.Map<String,String> environment,
                         String dir,
                         ProcessBuilder.Redirect[] redirects,
                         boolean redirectErrorStream)
        throws IOException
    {
        String envblock = ProcessEnvironment.toEnvironmentBlock(environment);//为null

        FileInputStream  f0 = null;//标准输入
        FileOutputStream f1 = null;//标准输出
        FileOutputStream f2 = null;//标准错误输出

        try {
            long[] stdHandles = new long[] { -1L, -1L, -1L };//此处有个if分支根据redirects参数处理标准输入输出重定向,这里删掉了。

            return new ProcessImpl(cmdarray, envblock, dir,
                                   stdHandles, redirectErrorStream); //我们重点看这里,执行简单命令,只有cmdarray(为[xxx.sh,aaa,bbb])和stdHandles以及redirectErrorStream(为false)有值,其他都为null
        } finally {
           //处理f0,f1,f2的关闭工作,这里删除。
        }

    }

我们接着往下看,下面代码将调用native方法创建进程并执行cmd

    private ProcessImpl(String cmd[],
                        final String envblock,
                        final String path,
                        final long[] stdHandles,
                        final boolean redirectErrorStream)
        throws IOException
    {
        String cmdstr = createCommandLine(
                VERIFICATION_LEGACY,
                executablePath,
                cmd); //要执行的cmd字符串

        handle = create(cmdstr, envblock, path,
                        stdHandles, redirectErrorStream);//调用native函数创建新进程并执行cmd
    }        

3 深入到openjdk的c程序

以最新的代码为例jdk-23为例说明,github地址:https://github.com/openjdk/jdk

linux相关代码在如下路径:

  • jdk-master\src\java.base\unix\native\libjava\ProcessImpl_md.c
  • jdk-master\src\java.base\unix\native\libjava\childproc.c

在ProcessImpl_md.c中,调用的是如下方法

static pid_t
forkChild(ChildStuff *c) { //参数是一个结构体,包含了要执行的cmd命令
    pid_t resultPid;
    resultPid = fork();

    if (resultPid == 0) { //只有子进程才会进入if分支
        childProcess(c); //子进程中执行该方法
    }
    assert(resultPid != 0);  /* childProcess never returns */
    return resultPid;
}

我们看看childProcess()方法

int
childProcess(void *arg)//参数是一个结构体,包含了要执行的cmd命令
{
    const ChildStuff* p = (const ChildStuff*) arg;
    int fail_pipe_fd = p->fail[1];

    JDK_execvpe(p->mode, p->argv[0], p->argv, p->envv);

    return 0;
}

我们接着往下看JDK_execvpe函数

void
JDK_execvpe(int mode, const char *file,
            const char *argv[],
            const char *const envp[])
{
    if (envp == NULL || (char **) envp == environ) {
        execvp(file, (char **) argv);
        return;
    }
}

这里执行的linux内核系统调用execvp,用于在新创建的进程中执行一个新的程序。

 execvp(file, (char **) argv); 

至此,代码分析完毕

4 为什么java中不能执行source命令

source命令是bash程序的build-in命令,而java执行shell命令时并不是创建一个bash再执行这个shell,而是在新创建的进程中执行这个shell。也就是说,java执行shell不是在bash中执行的。因此java中不能执行source命令。

 

标签:shell,java,String,cmd,cmdarray,源码,new,执行
From: https://www.cnblogs.com/zhenjingcool/p/17929194.html

相关文章

  • 排查java代码慢-arthas工具
    1.下载地址,arthas(gitee.io)2.下载的是一个zip包 3.上传到服务器任意位置,解压 4.查看java进程ps-ef|grep进程名称显示结果的进程号是:1098156.cd到arthas根目录,执行命令java-jararthas-boot.jar109815启动成功如下:注意:如果是用systemctl启动的,注意下......
  • 深入探讨Java反射:解析机制与应用场景
    当谈及Java编程语言的强大功能时,反射(Reflection)是一个不可忽视的特性。反射允许程序在运行时检查和操作其自身的结构,这为开发者提供了一种动态获取信息和执行操作的途径。在本篇博客中,我们将深入探讨Java反射的原理、用法以及一些实际场景中的应用。什么是反射?反射是Java的一种......
  • 无涯教程-Java9 - Collection工厂方法
    使用Java9,新的工厂方法被添加到List,Set和Map接口以创建不可变的实例。用于以较少的冗长和简洁的方式创建集合。Collections旧方法importjava.util.ArrayList;importjava.util.Collections;importjava.util.HashMap;importjava.util.HashSet;importjava.util.List;im......
  • Java JDBC 详解、使用、连接池
    JDBC介绍Java数据库连接,JDBC(JavaDatabaseConnectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。简单说,jdbc是Java语言为了屏蔽具体的具体的数据库操作的细节不同提供的一个框架。在关系型数据库的处......
  • 如何 使 Java、C# md5 加密的值保持一致
    JavaC#md5加密值保持一致,一般是编码不一致造成的值不同JAVA(加密:123456)C#(加密:123456)UTF-8e10adc3949ba59abbe56e057f20f883eUTF8e10adc3949ba59abbe56e057f20f883eUTF-16LEce0bfd15059b68d67688884d7a3d3e8cUnicodece0bfd15059b68d67688884d7a3d3e8cUS-ASCIIe10adc3949ba59a......
  • java状态模式
    1定义一个接口2publicinterfaceState{3publicvoidhandle();4}567/**8*具体的状态角色(下单)9*CreatedbyAdministrator10*/11publicclassPlaceAnOrderimplementsState{12//具体化状态的行为13@Override14public......
  • APP开发详解:数字药店系统源码
    数字药店系统的兴起,不仅为消费者提供了更加便捷的购药体验,也为药店管理和药品销售带来了全新的机遇。一、明确系统的基本功能:1.用户注册与登录2.药品浏览与搜索3.购物车与结算。4.在线支付与订单管理二、开发环境与技术栈选择前端开发环境通常使用React、Vue或Angular等流行的前端......
  • Java注解
    Java注解用于为Java代码提供元数据可以把注解当作一个标签注解的定义:public@interfaceAAA{}此时就相当于创建了一个名为AAA的注解(标签)注解的应用:@AAApublicclasstest{}此时就是把这个......
  • 写写java中的optional
    当我们写代码的时候经常会碰见nullpointer,所以在很多情况下我们需要做各种非空的判断。JDK8中引入了optional,他是一个包装好的类,我们可以把对象传入optional对象中,接下来就可以在optional中进行安全的消费一般使用的都是optional的方法ofNullable,这样当对象为null的时候会顺利执......
  • 大语言模型生成模型的源码结构复习
    modeling_gpt2.py:1099iflabelsisnotNone:#movelabelstocorrectdevicetoenablemodelparallelismlabels=labels.to(lm_logits.device)#Shiftsothattokens<npredictnshift_logits=lm......