java调⽤shell脚本和命令-ProcessBuilder⽅式
java调⽤shell脚本和命令-ProcessBuilder⽅式
在上⼀节中,我使⽤Runtime的⽅式,实现了对命令⾏和脚本⽂件的运⾏。最后我留下了⼀个⼩⼩的问题那就是这个InputStream和errorStream需要分开获取,那有没有其他⽅,不⽤分开获取流呢?
1.相关⽂档
答案当然是有的,这就是这章节要使⽤的ProcessBuilder⽅式了。
同样的,也先看下ProcessBuilder的API吧。
类⽤于创建操作系统进程。
每个 ProcessBuilder 实例管理⼀个进程属性集。start()) ⽅法利⽤这些属性创建⼀个新的 Process 实例。start()) ⽅法可以从同⼀实例重复调⽤,以利⽤相同的或相关的属性创建新的⼦进程。
每个进程⽣成器管理这些进程属性:
命令 是⼀个字符串列表,它表⽰要调⽤的外部程序⽂件及其参数(如果有)。在此,表⽰有效的操作系统
命令的字符串列表是依赖于系统的。例如,每⼀个总体变量,通常都要成为此列表中的元素,但有⼀些操作系统,希望程序能⾃⼰标记命令⾏字符串——在这种系统中,Java 实现可能需要命令确切地包含这两个元素。
环境 是从变量 到值 的依赖于系统的映射。初始值是当前进程环境的⼀个副本(请参阅 v())。
⼯作⽬录。默认值是当前进程的当前⼯作⽬录,通常根据系统属性 user.dir 来命名。
redirectErrorStream 属性。最初,此属性为 false,意思是⼦进程的标准输出和错误输出被发送给两个独⽴的流,这些流可以通过
修改进程构建器的属性将影响后续由该对象的 start()) ⽅法启动的进程,但从不会影响以前启动的进程或 Java ⾃⾝的进程。
⼤多数错误检查由 start()) ⽅法执⾏。可以修改对象的状态,但这样 start()) 将会失败。例如,将命令属性设置为⼀个空列表将不会抛出异常,除⾮包含了 start())。
**注意,此类不是同步的。**如果多个线程同时访问⼀个 ProcessBuilder,⽽其中⾄少⼀个线程从结构上修改了其中⼀个属性,它必须保持外部同步。
要利⽤⼀组明确的环境变量启动进程,在添加环境变量之前,⾸先调⽤ Map.clear()
这个API的解释中有⼀处特别的说明redirectErrorStream这个属性。这个设置为true即可实现流的合并操作。
看完了相关的⽅法的API吧。
构造⽅法摘要
ProcessBuilder(List<String> command) 利⽤指定的操作系统程序和参数构造⼀个进程⽣成器。
command) 利⽤指定的操作系统程序和参数构造⼀个进程⽣成器。
⽅法摘要
List<String>command() 返回此进程⽣成器的操作系统程序和参数。
ProcessBuilder command(List<String> command) 设置此进程⽣成器的操作系统程序和参数。
ProcessBuilder command) 设置此进程⽣成器的操作系统程序和参数。
File directory() 返回此进程⽣成器的⼯作⽬录。
ProcessBuilder directory(File directory) 设置此进程⽣成器的⼯作⽬录。
Map<String,String>environment() 返回此进程⽣成器环境的字符串映射视图。
boolean redirectErrorStream() 通知进程⽣成器是否合并标准错误和标准输出。
ProcessBuilder redirectErrorStream(boolean redirectErrorStream) 设置此进程⽣成器的 redirectErrorStream 属性。
Process start() 使⽤此进程⽣成器的属性启动⼀个新进程。
Process start() 使⽤此进程⽣成器的属性启动⼀个新进程。
⽅法摘要
好了接下来就开始代码实现了。
2.基础代码实现
这个过程与Runtime也基本⽆⼆异。就是将命令⾏参数传递给ProcessBuilder.将errorStream与inputStram合并,即设置redirectErrorStream属性为true
ProcessBuilder processBuilder =new ProcessBuilder(command);
接下来就是将读取流的信息即可。
来看下完整的代码实现:
public class SynchronousLocalShellCommand {
private Logger logger = Logger(SynchronousLocalShellCommand.class);
/** 命令信息 */
private final String command;
public SynchronousLocalShellCommand(String command){
thismand = command;
}
/**
* 执⾏命令并返回结果
*
* @return 命令执⾏结果
*/
public String doCommand(){
ProcessBuilder processBuilder =new ProcessBuilder(command);
try{
/
/ 将错误输出流转移到标准输出流中,但使⽤Runtime不可以
Process process = processBuilder.start();
String dataMsg =InputStream());
int rsp = process.waitFor();
logger.info("run command {}, response {}", command, rsp);
return dataMsg;
}catch(IOException | InterruptedException e){
<("command : {} ,exception", command, e);
}
return null;
shell脚本返回执行结果}
/**
* 数据读取操作
*
* @param input 输⼊流
*/
private String reader(InputStream input){
StringBuilder outDat =new StringBuilder();
try(InputStreamReader inputReader =new InputStreamReader(input, StandardCharsets.UTF_8);        BufferedReader bufferedReader =new BufferedReader(inputReader)){
String line;
while((line = adLine())!= null){
outDat.append(line);
outDat.append(Symbol.LINE);
}
}catch(IOException e){
<("command : {} ,exception", command, e);
}
String();
}
}
2.1 环境测试-windows
先来看下测试的代码:
public class TestSynchronousLocalShellCommand {
/** 同步执⾏命令 */
@Test
public void synchornousDoCommand(){
// 运⾏⼀个正常的命令
this.runCommand("ping www.baidu");
// 运⾏⼀个bat脚本
this.runCommand("D:/run/bat/run.bat");
// 错误命令
this.runCommand("abcdef");
}
/
**
* 运⾏command
*
* @param commandStr 错误命令⾏
*/
private void runCommand(String commandStr){
SynchronousLocalShellCommand command =new SynchronousLocalShellCommand(commandStr);    String commandRsp = command.doCommand();
Assert.assertNotNull(commandRsp);
System.out.println(commandRsp);
}
}
2.2 ⾸先运⾏的问题
这⾥就可以看到结果:
11:57:58.177[main] ERROR com.liujunmand.processbuilder.SynchronousLocalShellCommand - command : ping www.baidu ,exception java.io.IOException: Cannot run program "ping www.baidu": CreateProcess error=2,系统不到指定的⽂件。
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
at com.liujunmand.processbuilder.SynchronousLocalShellCommand.doCommand(SynchronousLocalShellCommand.java:44)
at com.liujunmand.processbuilder.TestSynchronousLocalShellCommand.runCommand(TestSynchronousLocalShellCommand.java:33)
at com.liujunmand.processbuilder.TestSynchronousLocalShellCommand.synchornousDoCommand(TestSynchronousLocalShellCommand.java:18) flect.NativeMethodAccessorImpl.invoke0(Native Method)
flect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
flect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at flect.Method.invoke(Method.java:498)
at org.del.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.del.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.del.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at mockit.integration.junit4.uteTestMethod(JUnit4TestRunnerDecorator.java:162)
at mockit.integration.junit4.internal.JUnit4TestRunnerDecorator.invokeExplosively(JUnit4TestRunnerDecorator.java:71)
at mockit.integration.junit4.internal.MockFrameworkMethod.invokeExplosively(MockFrameworkMethod.java:37)
at org.del.FrameworkMethod.invokeExplosively(FrameworkMethod.java)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.io.IOException: CreateProcess error=2,系统不到指定的⽂件。
at java.ate(Native Method)
at java.lang.ProcessImpl.<init>(ProcessImpl.java:444)
at java.lang.ProcessImpl.start(ProcessImpl.java:140)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
...29 common frames omitted
java.lang.AssertionError
at com.liujunmand.processbuilder.TestSynchronousLocalShellCommand.runCommand(TestSynchronousLocalShellCommand.java:34)
at com.liujunmand.processbuilder.TestSynchronousLocalShellCommand.synchornousDoCommand(TestSynchronousLocalShellCommand.java:18)这个结果居然是提⽰不到命令。那是哪⾥出现问题了?
2.3 问题的解决⽅案
像在命令⾏⼀般提⽰不到指定⽂件,我的第⼀想法就是在系统上下⽂中不到指定的运⾏⽂件。⽽像ping这样命令如果在windows运⾏的话,第⼀步当然打开,然后输⼊ping www.baidu就能看到了。
经过这样⼀分析,我这个程序的运⾏是缺少了上下⽂了,那如何添加上下⽂呢?
当检查了ProcessBuilder后发现这个参数是接受⼀个集合为参数的,将命令⾏添加即可。但还存在⼀个
问题,那就是当我⼿动执⾏时,是分成了两个步骤先是打开cmd,然后输⼊ping命令。⽽我执⾏是需要⼀次执⾏命令的。这该怎么办呢?
这时候不访看看cmd的帮助⽂档?
C:\Users\liujun>cmd /?
启动 Windows 命令解释器的⼀个新实例
CMD [/A | /U] [/Q] [/D] [/E:ON | /E:OFF] [/F:ON | /F:OFF] [/V:ON | /V:OFF]
[[/S] [/C | /K] string]
/C 执⾏字符串指定的命令然后终⽌