Java执行Shell命令笔记
ProcessBuilder类浅析以及Linux Shell执行逻辑
从Java代码运行Shell命令有两种方式
Runtime
类的
exec
方法
ProcessBuilder
类
使用第二种方法能够定制更多的内容,本文主要介绍的是第二种,第一种介绍下用法
环境约定:
Arch Linux x86_64 Linux 5.4.44-1-lts
java version "1.8.0_251" Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)
Runtime
1 |
public void runTime() throws IOException, InterruptedException { |
一个很简单的例子,执行
ls -al
命令获取项目文件夹下的文件信息
查看源码,在JDK1.0的时候就提供,使用单例模式返回一个Runtime对象
在
exec
方法中,实际最终也是调用了
ProcessBuilder
这个类去执行,所以我们研究
ProcessBuilder即可
ProcessBuilder
简介
- 此类用于创建操作系统进程
- 命令的有效性取决于操作系统
- 此类 未同步 。 如果多个线程同时访问ProcessBuilder实例,并且至少有一个线程在结构上修改了其中一个属性,则必须在外部对其进行同步
-
主要作用是添加了
/home/cc/sofeware/node/bin/环境变量,File.pathSeparator值实际为:,这样我们就等于给先获得当前环境变量,然后把我们想要添加的环境变量通过:连接进而添加到PATH -
w2程序依赖了node.js,node.js位于/home/cc/sofeware/node/bin/,所以得给加上PATH加上该变量,以便w2程序运行时能够从PATH中找到node.js -
command("/home/cc/sofeware/node/bin/w2")能不能改为command("w2"),因为w2也在/home/cc/sofeware/node/bin/不行的,因为这里是用Java程序的环境变量去执行的w2,自然是找不到w2,但是我们可以使用Shell去执行,因为当前PATH是有bash的环境变量,当bash启动后,新的PATH会传递给bash,然后bash就可以找到w2
1
processBuilder.command("bash", "-c", "w2 stop -D /home/cc/test/whistle/config1");
或者是我们可以改变ProcessBuilder的工作目录,然后用
./w2启动,./表示当前目录1
2ProcessBuilder pb = new ProcessBuilder("./w2", "help");
pb.directory(new File("/home/cc/sofeware/node/bin/"));使用修改后的工作目录启动进程
默认值是当前进程的当前工作目录,通常是由系统属性
user.dir命名的目录1
2
3
4
5
6
7
8public void processBuilder() throws IOException {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("ls");
processBuilder.directory(new File("/home/cc"));
Process process = processBuilder.start();
String s = IoUtil.read(process.getInputStream(), "UTF-8");
System.out.println(s);
}上面程序我们将工作目录改为
/home/cc需要注意的是,如果我们想要执行某个文件夹下的可执行文件:先使用
directory方法定位到该文件夹,然后使用command命令去执行,这样是不行的。因为command会从环境变量中找,而不是当前目录,如果要使用,记得加
./表示当前目录重定向标准输入和输出
1
2
3Process processBuilder = new ProcessBuilder("ls", "-l")
.redirectOutput(new File("/home/cc/ProcessBuilder.log"))
.start();上面是重定向结果输出到文本中,内容会覆盖
追加到日志文件而不是每次创建一个新文件时:
1
2
3
4Process processBuilder = new ProcessBuilder("ls", "-l")
.redirectOutput(ProcessBuilder.Redirect.appendTo(new File("/home/cc/ProcessBuilder.log")))
.start();
}上面都不包括错误输出,输出错误:
1
Process processBuilder = new ProcessBuilder("ls", "-l").redirectErrorStream(true)
如有任何错误,错误输出将合并到正常过程输出文件中
也可以错误输出和内容输出定位到不同文本
1
2
3
4
5Process processBuilder = new ProcessBuilder("ls", "-l")
.redirectErrorStream(true)
.redirectOutput((new File("/home/cc/ProcessBuilder.log")))
.redirectError(new File("/home/cc/ProcessBuilder_error.log"))
.start();How to Run a Shell Command in Java | Baeldung
Guide to java.lang.ProcessBuilder API | Baeldung
lang包源码解读之ProcessBuilder_凌霄的专栏-CSDN博客_processbuilder包
Linux Shell执行逻辑
什么是Shell
Shell是用户与操作系统的接口,是操作系统的最外层。Shell结合了一种编程语言来控制进程和文件,以及启动和控制其他程序。
Shell通过解析用户输入,然后处理操作系统产生的任何输出,来管理用户和操作系统之间的交互。
Shell的建立(Linux 0.11)
开机到执行main函数之前
然后程序加载第一部分内核代码—引导程序(bootsect),其作用是陆续把银盘的操作程序加载入内存,接着bootsect【划分内存并加载第二部分内核代码—setup】和【加载第三部分内核代码—system模块】,之后setup程序开始运行,做的第一件事就是从设备上提取内核运行所需的机器系统数据
接下来是向32位模式转变,开始设置GDT和IDT(中断描述符和全局描述符表),打开A20地址线,实现32位寻址,为保护模式下执行
head.s(system模块第一部分代码)做准备,head.s开始执行,重建GDT,建立内核分页机制,然后执行ret指令跳到main函数程序执行设备环境初始化以及激活进程0
接着初始化内存管理结构
mem_map(对1MB以上的内存分页进行管理,记录一个页面的使用次数),开机启动时间设置(结合主板上面的一个小存储芯片CMOS上面记录的时间数据)等然后初始化进程0(进程0是运行的第一个进程,是Linux操作系统父子进程创建机制的第一个父进程)。具备支持多线程轮流执行,处理系统调用等能力,这样才能保证将来在主机正常地运行,并将这些能力遗传给后续建立的进程
最后开启中断,初始化缓冲区管理结构,硬盘等 ,以及用仿中断的方法将进程0的特权级由0变成3(Linux系统规定除进程0之外,所有进程都要由一个已有进程在3特权级下创建,进程0的代码和数据都是由操作系统的设计者写在内核代码、数据区,并且此前还处在0特权级,严格说还不是真正意义上的进程),实现激活进程0
进程1的创建及执行
设置进程1的分页管理和进程1在GDT中的表项,将进程1的状态设置为就绪态,使它可以参与进程调度。内核第一次调度,进程0切换到进程1执行,进程1第一次执行后开始设置硬盘信息,格式化虚拟盘,加载根文件系统等工作
进程2的创建
进程2执行以及加载shell程序
接着加载参数和环境变量到进程2的栈空间中。进程2有了主机对应的程序shell,因此要调整自己对应的管理结构和EIP、ESP,这样软中断iret返回后,进程2将从shell程序开始执行。
执行shell程序
系统创建update进程(有一项很重要的任务:将缓冲区中的数据同步到外设,为了提高系统整体效率),重建shell之后,操作系统用户将通过shell进程提供的平台与计算机交互。
shell处理用户指令工作原理
用户通过键盘输入的信息,存储在指定的字符缓冲队列上。该缓冲队列上的内容,就是tty0文件的内容。shell进程会不断读取缓冲队列上的数据信息。如果用户没有下达指令,缓冲队列中就不会有数据。
shell进程将会被设置为可中断等待状态,即被挂起。如果用户通过键盘下达指令,将产生键盘中断,中断服务程序会将字符信息存储在缓冲队列上,并给shell
进程发信号,信号将导致shell进程被设置为就绪状态,即被唤醒,唤醒后的shell继续从缓冲队列中读取数据信息并处理,完毕后,shell进程将再次被挂起,等待下一次键盘中断被唤醒。
简单使用
1 |
public void processBuilder() throws IOException, InterruptedException { |
这里需要注意的是传入的命令是分割后的,格式
new ProcessBuilder("cmd", "arg1", "arg2", ...);
如果命令太长,我们可以使用
split()
方法分割,或者是使用
bash -c
:
1 |
.command("/usr/bin/java -Djava.library.path=/Users/myusername/myproject/lib/DynamoDBLocal_lib/ -jar /Users/myusername/myproject/lib/DynamoDBLocal.jar -sharedDb".split("\\s+")).start(); |
各个实例环境变量独立
1 |
public void processBuilder() throws IOException, InterruptedException { |
1 |
PATH: /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl |
创建时ProcessBuilder时,该值都会初始化为当前环境变量的副本,每个ProcessBuilder实例始终包含独立的环境中,对每个ProcessBuilder实例修改环境变量并不会影响到别的ProcessBuilder实例获取到的环境变量的值
从上面我们也可以看到,每次返回的都是一个
clone()
后的对象
environ()
是个native方法,返回的是我们上面打印出来的那些环境变量
ProcessEnvironment
类有个
String getenv(String name)
方法
根据key返回
theUnmodifiableEnvironment
里面对应的值,从上面的图可以看出,
theUnmodifiableEnvironment
存储的应该是环境变量,加上注释说该方法仅用于
System.getenv(String)
,所以我们可以用该方法获取环境变量
修改环境变量
1 |
public void processBuilder() throws IOException, InterruptedException { |