Java调用shell脚本

  • Post author:
  • Post category:java


也许在某种情况下,Java程序需要调用shell脚本才能完成,
比如为了从其他服务器上下载一些文件,但是却不能使用普通的sftp代码完成,需要使用到证书,
这时shell脚本就比较方便
在这里我写了一个工具类,是为了调用shell脚本的。代码如下:
    public class JaveShellUtil {

    public static int ExecCommand(String command) {
        int retCode = 0;
        try {
            Process process = Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", command }, null, null);
            retCode = process.waitFor();
            ExecOutput(process);
        } catch (Exception e) {
            retCode = -1;
        }
        return retCode;
    }

    public static boolean ExecOutput(Process process) throws Exception {
        if (process == null) {
            return false;
        } else {
            InputStreamReader ir = new InputStreamReader(process.getInputStream());
            LineNumberReader input = new LineNumberReader(ir);
            String line;
            String output = "";
            while ((line = input.readLine()) != null) {
                output += line + "\n";
            }
            input.close();
            ir.close();
            if (output.length() > 0) {
            }
        }
        return true;
    }

}

测试代码如下:

(1)不需要传递参数仅仅是为了调用shell脚本

public class MyShell {

    public static void main(String[] args) {
        String command="cfg/demo.sh";
        //需要传递的就是shell脚本的位置
        int retCode=JaveShellUtil.ExecCommand(command);
        System.out.println(retCode+"retCode");
        if(retCode==0){
            System.out.println("success.....");
        }else{
            System.out.println("error.....");
        }
    }

}

(2)传递参数,为了在shell脚本中获得对应的参数,比如使用shell脚本创建某一个时间的文件夹时

package com.zhou.dado.sehll;
import com.zhou.dado.util.JaveShellUtil;
public class MyShell {

    public static void main(String[] args) {
        String command="cfg/demo.sh";
        //需要传递的就是shell脚本的位置,参数的话每一个之间需要使用空格隔开,务必
        int retCode=JaveShellUtil.ExecCommand(command+" "+"zhoudado"+" "+"barnamefile"+" "+"bxmnamefile");
        System.out.println(retCode+"retCode");
        if(retCode==0){
            System.out.println("success.....");
        }else{
            System.out.println("error.....");
        }
    }

}


注:在传递参数时,command后面必须要有一个空格,也就是说每个的参数后面都需要有一个空格才可以,我这里是传递了三个参数

测试:

(1)将其程序部署在Linux服务器

(2)在程序的cfg/路径下编写demo.sh,注意,因为shell脚本可能会出现window换行和空格问题,一定要在Linux环境下编写,否则打死都找不到问题所在

比如:

#!/usr/bin/env bash
param1=$1
param2=$2
param3=$3
basepath="/app/app/$param1"
mkdir -p $basepath

(3)在Linux环境下找到项目路径,比如我的是 cd /app/app/javashell/

然后使用命令启动Java的main方法

java -cp bin:./lib/* com.zhou.dado.sehll.MyShell

可能出现的问题:

(1)返回代码为126,这是因为你的demo.sh脚本没有x的权限,一般情况下会有r w x的权限的,如果没有则需要赋给r权限

chmod +x demo.sh

(2)返回代码为127,这个是因为没有找到命令,业务shell脚本有些空格什么问题的原因,注意编写shell脚本的规则

(3)返回码为1,一般性未知错误

(4)返回码为2,不适合的shell命令

(5)返回码为128,无效的退出参数

(6)返回码为128+x,与Linux信号x相关的严重错误

(7)返回码为130 通过Ctrl+C终止的命令

(8)返回码为255 正常范围内之外的退出状态码

(9)返回码为0 恭喜您,成功调用


我将程序会上传到我的csdn中,文件名为Javashell,其中也会有调用sftp的shell脚本,仅供参考,需要的请自行下载,哪里有不周的地方,望见谅

注意:执行shell脚本是通过java的Runtime.getRuntime().exec()方法调用的。这种调用方式可以达到目的,但是他在java虚拟机中非常消耗资源,即使外部命令本身能很快执行完毕,频繁调用时创建进程的开销也非常可观。java虚拟机执行这个命令的过程是:首先克隆一下和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后在退出这个进程,如果频繁执行这个操作,系统的消耗会很大,不仅是CPU,内存的负担也很重。

引入一个案列:

一个数字校园应用系统,运行在一台4个CPU的Solaris10操作系统上,中间件为ClassFish服务器,系统在进行大并发压力测试的时候,发现请求响应时间比较慢,通过操作系统的mpstat工具发现CPU使用率很高,并且占用绝大多数CPU资源的程序并不是应用系统本身,这是个不正常的现象。通常情况下用户应用的CPU占有率应该占有主要地位,才能说明系统是正常工作的。

通过Solaris10的Dtrace脚本可以查看当前情况下消耗CPU资源的竟然是fork系统调用。fork系统调用时Linux用来产生新进程的,在java虚拟机中,用户编写的java代码最多只有线程的概念,不应当有进程的产生。

这是个非常异常的现象,通过查找:每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息。执行shell脚本是通过java的Runtime.getRuntime().exec()方法调用的。这种调用方式可以达到目的,但是他在java虚拟机中非常消耗资源,即使外部命令本身能很快执行完毕,频繁调用时创建进程的开销也非常可观。java虚拟机执行这个命令的过程是:首先克隆一下和当前虚拟机拥有一样环境变量的进程,再用这个新的进程去执行外部命令,最后在退出这个进程,如果频繁执行这个操作,系统的消耗会很大,不仅是CPU,内存的负担也很重。

用户根据建议去掉这个shell脚本执行的语句,改为使用java的API去获取这些信息后,系统很快就恢复正常



版权声明:本文为zhoudado921原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。