最近开发IntelliJ插件的时候,有一个需求是使用Java代码来执行命令行脚本。网上找到的解决方案都是大同小异,但是不同的需求有不同的解决方案,在需求完成以后做了一些总结:
如下表所示为可能出现的关于命令行的不同需求:
MacWin打开命令行工具 && 指定目录Terminal / iTerm2cmd后台执行后台执行命令单个命令单个命令执行脚本.sh.bat
0. 准备
Java执行命令行脚本的最通用的方法就是 Runtime.getRuntime().exec(),其有六个重载方法,如下所示。通过传入不同的参数可以实现不同的需求。
上述在使用Runtime.getRuntime().exec()的过程中有三个参数,具体的概括如下:第一个参数(必要参数)是String类型(或 String[]类型),来表示传入命令;
第二个参数(非必要参数)代表的命令在指定的环境(如Windows下的环境变量)下执行;
第三个参数(非必要参数)代表命令行在某个文件夹目录下执行;
Mac上默认的命令行工具是Terminal,同时用的最多的第三方的也有iTerm2。而在Windows上默认的命令行工具是cmd。
Mac命令行工具的地址:
Windows命令行工具地址:
1. 打开命令行工具 && 并指定目录
MacOS和Windows两个环境下,指定目录的方法有些不同:在MacOS直接对exec()方法的第一个参数进行操作即可,即 使用命令行工具打开指定目录。如果在第一个参数中没有指定目录,则打开的命令行工具默认地址是该命令行工具的默认路径下。(指定exec()第三个参数无效)
在Windows下需要传入exec()第三个参数进行目录指定。第一个参数用来打开Cmd。如果不指定目录,则Cmd的执行目录与当前程序执行的目录相同。
MacOS
Windows
2. 后台执行命令
有时在从Java底层去执行命令效率会高一些,但不会出现可视化的命令行工具,因此,需要借助Java IO流来对一些执行后的信息输出打印。
由于Java程序给进程的输出流分配的缓冲区很小,但有时候当执行一些cli安装命令的时候(如执行npm install),返回的数据量非常大,同时程序又没有对进程的输出流进行处理,则缓冲区就会被塞满,从而导致exec()线程永久阻塞,进程也会停滞,直到Java程序结束或输出流被垃圾回收处理。
因此我们需要开两个线程分别去处理标准输出流和错误输出流:
MacOS
Windows
上述的两个方法分别是在MacOS和Windows环境下,在xx文件夹执行npm install的命令,并重开一个线程进行输出流的打印。
tips:在MacOS环境中,如果想要在执行完毕npm install后再去执行其他命令如ls -a、npm -v等,上述方法没法满足要求,如有需求则可以重新开一个Process来继续执行。但是在Windows环境下,可以在npm install后添加&&再去输入其他命令,如:npm install && npm -v;
在上述两个环境中,均可以通过传入Runtime.getRuntime().exec()第三个参数来指定命令行执行目录;
在Windows环境下,cmd后面需要加上/c才能执行,否则Java会在path中去找npm install.exe来执行;
3. 在命令行工具中执行命令&&脚本
上面是使用Java在不调用命令行工具的前提下在后台执行命令的方法,如果想要打开命令行工具并执行命令,则可以通过构建脚本的方式来满足需求。
MacOS环境下可以执行.sh文件的脚本,而在Windows环境下则可以执行.bat文件的脚本。在构造好相应的脚本后,使用命令行工具来打开该脚本即可。
如下代码所示,在执行过程中构造.sh和.bat文件。如果有提前构造好的.sh和.bat脚本,可以将脚本在电脑中的存放路径(String),直接替换到下面file.getAbsolutePath()地方。
MacOS
Windows
tips.sh文件脚本中,可以添加多个多个命令来供MacOS的命令行工具执行。
.bat文件脚本中,在执行完第一个命令后不能再执行第二个命令,如果有需求可以通过在第一个命令后添加 && command2的方法来执行第二个命令。
4. 总结
上述三点通过介绍Runtime.getRuntime().exec()方法的使用,来满足某些使用Java代码处理命令行脚本的需求。主要有下面三点:分别在MacOS和Windows环境下直接打开命令行工具,并定位到指定目录下;
在不调用命令行工具的前提下,使用Java代码在后台执行命令,并打印出执行结果;
调用命令行工具分别在MacOS和Windows环境下执行.sh和.bat脚本;
That's All.
笔记首发于微信公众号,有兴趣的搜索Toryang的笔记,即可关注订阅!