随笔记

平凡人平凡路,沉下心迈出步

0%

聊一聊Java调用外部脚本

Java如何在Linux服务器上调用shell、py等可执行文件

通常你会这么做:

1
2

Runtime.getRuntime().exec(`你想要的脚本`)

实时上这样写不会生效

正确的姿势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

String[] commands = {"/bin/sh", "-c", cmd};
Process process = null;
try {
process = Runtime.getRuntime().exec(commands);
try (InputStream is = process.getInputStream();
BufferedReader stdInput = new BufferedReader(new InputStreamReader(is));
BufferedReader stdError = new BufferedReader(new InputStreamReader(is))) {
char[] data = new char[bufferSize];
stdInput.read(data, 0, data.length);
stdInputInfo.append(data, 0, data.length);
stdError.read(data, 0, data.length);
stdErrorInfo.append(data, 0, data.length);
} catch (Exception ignore) {
log.error(ignore.getMessage(), ignore);
}
process.wait();
} catch (IOException e) {
log.error("执行命令失败:{}", e);
} finally {
if (process != null) {
process.destroy();
}
}

如何添加超时机制

  1. 可以看到上面的写法会阻塞等待,若指令是一个读取大文件、或者一个长时间业务脚本,则会导致业务系统阻塞,这时就需要考虑超时机制了。

  2. 如果是使用JDK1.8+ 可以这样做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    String[] commands = {"/bin/sh", "-c", cmd};
    Process process = null;
    try {
    process = Runtime.getRuntime().exec(commands);
    if (!process.waitFor(timeout, TimeUnit.MILLISECONDS)) {
    process.destroyForcibly();
    process = null;
    log.info("time out");
    }
    try (InputStream is = process.getInputStream();
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(is));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(is))) {
    char[] data = new char[bufferSize];
    stdInput.read(data, 0, data.length);
    stdInputInfo.append(data, 0, data.length);
    stdError.read(data, 0, data.length);
    stdErrorInfo.append(data, 0, data.length);
    } catch (Exception ignore) {
    log.error(ignore.getMessage(), ignore);
    }
    } catch (IOException e) {
    log.error("执行命令失败:{}", e);
    } finally {
    if (process != null) {
    process.destroy();
    }
    }
  3. 如果是低版本JDK则考虑使用线程Join的方式来包裹指令来实现

JDK1.8中Linux的waitFor实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean waitFor(long timeout, TimeUnit unit)
throws InterruptedException
{
long startTime = System.nanoTime();
long rem = unit.toNanos(timeout);

do {
try {
exitValue(); /* step 1 调用后如果未退出,则抛出异常*/
return true;
} catch(IllegalThreadStateException ex) { /* catch 到step1 的异常后执行sleep方法以达到超时的需要*/
if (rem > 0)
Thread.sleep(
Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
}
rem = unit.toNanos(timeout) - (System.nanoTime() - startTime);
} while (rem > 0);
return false;
}
  1. 进程关闭不完全的问题如何处理,目前遇到在脚本中通过管道、Python脚本等方式会存在父子进程,超时destroy只会关闭父进程,而子进程还是在继续执,这样并没有达到超时终止任务的目的。目前通过下面的方式处理的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    if (killAll) {
    Field f = process.getClass().getDeclaredField("pid");
    f.setAccessible(true);
    long pid = f.getInt(process);//获取父进程pid
    //通过pkill来关闭所有进程。防止子进程未结束
    Process kill = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "pkill -P " + pid});
    kill.waitFor();
    } else {
    process = process.destroyForcibly();//只会销毁当前进程
    process.waitFor();
    }

    process = null;