Java调用linux命令及Shell脚本

Java可以通过Runtime.getRuntime().exec()方法调用linux平台下的命令及Shell脚本。

获取命令执行结果通常有两种,一种是waitfor方法,另一种是exitValue。
但waitfor方法可能造成阻塞,原因如下:

当调用exec方法后,JVM启动一个子进程,该进程会与JVM进程建立3个管道连接,即标准输入流、标准输出流、错误错误流。假设该程序不间断向标准输出流和标准错误流写数据,而JVM不读取,那么数据会暂存在Linux缓冲区中,缓冲区写满后该程序将无法继续写入,程序就会一直阻塞在waitfor方法,永远无法结束。

解决方法就是增加两个线程,一个负责读取标准输出流,一个负责读取标准错误流,这样数据就不会积压在缓冲区,waitfor方法可以正常结束。

总之,调用外部程序时需要注意以下两点:
1、如果外部程序有大量输出,需要有单独线程读取输出流和错误流
2、必须关闭3个句柄——标准输入流、标准输出流、标准错误流

考虑到阻塞问题以及为了获取命令输出,文中使用了exitValue方法。

代码如下

ShellUtils:执行外部命令的工具类

package com.wll.shell;

import com.wll.utils.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class ShellUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(ShellUtils.class);

    private static final long THREAD_SLEEP_TIME = 10;

    private static final int DEFAULT_WAIT_TIME = 20 * 60 * 1000;


    public static void runShell(String cmd) {
        String[] command = new String[]{"/bin/sh", "-c", cmd};
        try {
            Process process = Runtime.getRuntime().exec(command);
            ShellResult result = getProcessResult(process, DEFAULT_WAIT_TIME);
            LOGGER.info("Command [{}] executed successfully.", cmd);
            LOGGER.info(result.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取命令执行结果
     * @param process 子进程
     * @param waitTime 指定超时时间
     * @return 命令执行输出结果
     */
    public static ShellResult getProcessResult(Process process, long waitTime) {
        ShellResult cmdResult = new ShellResult();
        boolean isTimeout = false;
        long loopNumber = waitTime / THREAD_SLEEP_TIME;
        long realLoopNumber = 0;
        int exitValue = -1;

        StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream());
        StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream());

        errorGobbler.start();
        outputGobbler.start();

        try {
            while (true) {
                try {
                    Thread.sleep(THREAD_SLEEP_TIME);
                    exitValue = process.exitValue();
                    break;
                } catch (InterruptedException e) {
                    realLoopNumber++;
                    if (realLoopNumber >= loopNumber) {
                        isTimeout = true;
                        break;
                    }
                }
            }

            errorGobbler.join();
            outputGobbler.join();

            if (isTimeout) {
                cmdResult.setErrorCode(ShellResult.TIMEOUT);
                return cmdResult;
            }

            cmdResult.setErrorCode(exitValue);
            if (exitValue != ShellResult.SUCCESS) {
                cmdResult.setDescription(errorGobbler.getOutput());
            } else {
                cmdResult.setDescription(outputGobbler.getOutput());
            }
        } catch (InterruptedException e) {
            LOGGER.error("Get shell result error.");
            cmdResult.setErrorCode(ShellResult.ERROR);
        } finally {
            CommonUtils.closeStream(process.getErrorStream());
            CommonUtils.closeStream(process.getInputStream());
            CommonUtils.closeStream(process.getOutputStream());
        }

        return cmdResult;
    }
}

StreamGobbler:读取命令输出流和错误流的工具类

package com.wll.shell;

import com.wll.utils.CommonUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class StreamGobbler extends Thread {
    private InputStream is;

    private List output = new ArrayList();

    public StreamGobbler(InputStream is) {
        this.is = is;
    }

    public List getOutput() {
        return output;
    }

    @Override
    public void run() {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String line = "";
            while ((line = reader.readLine()) != null) {
                output.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            CommonUtils.closeStream(reader);
        }
    }
}

ShellResult:命令执行结果

package com.wll.shell;

import java.util.List;

public class ShellResult {
    public static final int SUCCESS = 0;

    public static final int ERROR = 1;

    public static final int TIMEOUT = 13;

    private int errorCode;

    private List description;

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public List getDescription() {
        return description;
    }

    public void setDescription(List description) {
        this.description = description;
    }

    @Override
    public String toString() {
        return "ShellResult{" +
                "errorCode=" + errorCode +
                ", description=" + description +
                '}';
    }
}

ShellTest:测试类

package com.wll.shell;

public class ShellTest {
    public static void main(String[] args) {
        String cmd = "";
        if (args.length == 1) {
            cmd = args[0];
        }
        ShellUtils.runShell(cmd);
    }
}

另外,由于流关闭操作用得比较频繁,故单独写了个工具类。

package com.wll.utils;

import org.apache.log4j.Logger;

import java.io.Closeable;
import java.io.IOException;

public class CommonUtils {
    private static final Logger LOGGER = Logger.getLogger(CommonUtils.class);

    /**
     * 提供统一关闭流的方法
     *
     * @param stream 待关闭的流
     */
    public static void closeStream(Closeable stream) {
        if (stream == null) {
            return;
        }

        try {
            stream.close();
        } catch (IOException e) {
            LOGGER.error("Close stream failed!");
        }
    }
}

代码放在CentOS下,详细目录结构如下:

Java调用linux命令及Shell脚本_第1张图片

以下为测试脚本,十分简单,只是输出当前日期和时间

Java调用linux命令及Shell脚本_第2张图片


最终运行结果如下:


你可能感兴趣的:(Java,Linux)