我们需要借助Java的Process类调用shell脚本处理一些任务!
下面我主要介绍两点:
-
Java的Process类是什么
-
如何使用Process类操作Shell脚本指令
-
process 类操作shell脚本文件
-
Process类和ProcessBuilder源码
一、Java的 Process类
为了执行调用操作,JVM会启一个Process,Process类主要有以下几个方法:
public abstract class Process {
细心的读者会发现,为了执行调用操作,JVM会启一个Process,所以我们可以通过调用Process类的以下方法,得知调用操作是否正确执行:
1.1 waitFor()
//导致当前线程等待
//返回值是进程的出口值。0 表示正常终止;否则,就表示异常失败。
abstract int waitFor()
1.2 isAlive()
// 检测子进程是否存活,存活则返回 true
public boolean isAlive()
另外,调用某些Shell命令或脚本时,会有返回值,那么我们如果捕获这些返回值或输出呢?为了解决这个问题,Process类提供了:
1.2 getInputStream()
//获取子进程的输入流。 最好对输入流进行缓冲。
abstract InputStream getInputStream()
二、如何使用Process类操作Shell脚本指令
主要实现方式有两种:
-
直接使用 Process类处理进程
-
使用ProcessBuilder构造一个进程生成器
2.1 直接使用 Process类操作
在linux系统查看系统当前目录
2.2 使用ProcessBuilder构造一个进程生成器
使用Java的ProcessBuilder实现同样的功能
2.3 Runtime方式和ProcessBuilder方式对比
-
Runtime方式:此为最常见的一种运行方式,历史最悠久,使应用程序能够与其运行的环境相连接,但是在读取上还存在一些不便性,正常的输出流与错误流得分开读取。其他功能基本相同。
-
ProcessBuilder:此为jdk1.5加入的,它没有将应用程序与其运行的环境相连接。这个就需要自己设置其相关的信息。但它提供了将正常流与流程流合并在一起的解决办法,只需要设置redirectErrorStream(错误流重定向到标准数据流)属性即可,这样更有利于脚本执行过程中出现问题时排查。
三、Process类操作Shell脚本文件
假如我要调用的Shell脚本是文件:/root/experiment/test.sh
#!/usr/bin/env bash
args=1
if [ $# -eq 1 ];then
args=$1
echo "The argument is: $args"
fi
echo "This is a $call"
start=`date +%s`
sleep 3s
end=`date +%s`
cost=$((($end - $start) * $args * $val))
echo "Cost Time: $cost"
Java调用代码
public static void callShellScript_test() {
BufferedReader bufferedReader = null;
try {
String shellPath = CallShellScriptDemo01.class.getResource("/").getPath() + "shell/test.sh";
ProcessBuilder builder = new ProcessBuilder("/bin/sh", shellPath);
// 错误流重定向到标准输出流
builder.redirectErrorStream(true);
Process ps = builder.start();
int exitValue = ps.waitFor();
if (0 != exitValue) {
log.error("call shell failed. error code is :" + exitValue);
}
bufferedReader = new BufferedReader(new InputStreamReader(ps.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println("line = " + line.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
log.info("数据刷新完成");
}
四、Process类和ProcessBuilder源码
4.1 Process类
源代码虽是英文的,但是可以通过对原注释的阅读,直接体会jdk原创作者的真实想法,这样大家对process这个类会有更深一层次的理解。
4.2 ProcessBuilder
public final class ProcessBuilder
/** 源码中的注释
This class is used to create operating system processes. 此类用于创建操作系统进程
Each {@code ProcessBuilder} instance manages a collection of process attributes.
The {@link #start()} method creates a new {@link Process} instance with those attributes.
The {@link
* #start()} method can be invoked repeatedly--重复地 from the same instance
* to create new subprocesses with identical or related attributes.
*/
Each process builder manages these process attributes: 每个进程生成器都管理这些进程属性:
command</i>, a list of strings which signifies--表示... the
* external--外部-额外 program file to be invoked and its arguments, if any.
Which string lists represent-代表,扮演 a valid--有效的 operating system command is
* system-dependent.
* <li>an <i>environment</i>, which is a system-dependent mapping from
* <i>variables</i> to <i>values</i>. The initial value is a copy of
* the environment of the current process (see {@link System#getenv()}).
* <li>a <i>working directory</i>. The default value is the current
* working directory of the current process, usually the directory
* named by the system property {@code user.dir}.
Note that this class is not synchronized. -- 线程不安全
* If multiple threads access a {@code ProcessBuilder} instance
* concurrently, and at least one of the threads modifies one of the
* attributes structurally, it <i>must</i> be synchronized externally.
例子:
* <p>Starting a new process which uses the default working directory
* and environment is easy:
* <pre> {@code
* Process p = new ProcessBuilder("myCommand", "myArg").start();
* }</pre>
下面是一个示例,它使用修改的工作目录和环境启动进程,并重定向标准输出和错误*以附加到日志文件中: <p>Here is an example that starts a process with a modified working
* directory and environment, and redirects standard output and error
* to be appended to a log file:
ProcessBuilder pb =
* new ProcessBuilder("myCommand", "myArg1", "myArg2");
* Map<String, String> env = pb.environment();
* env.put("VAR1", "myValue");
* env.remove("OTHERVAR");
* env.put("VAR2", env.get("VAR1") + "suffix");
* pb.directory(new File("myDir"));
* File log = new File("log");
* pb.redirectErrorStream(true);
* pb.redirectOutput(Redirect.appendTo(log));
* Process p = pb.start();
* assert pb.redirectInput() == Redirect.PIPE;
* assert pb.redirectOutput().file() == log;
* assert p.getInputStream().read() == -1;
* <p>To start a process with an explicit set of environment
* variables, first call {@link java.util.Map#clear() Map.clear()}
* before adding environment variables.
有了这个方向,相信大家会少踩一些坑,而且大胆地使用java和脚本之间的交互。java可以调用shell,那么shell再调用其他就方便了。不过, 记得一点,不要过度地依赖缓冲区进行线程之间的通信。