怎么通过java去调用并执行shell脚本以及问题总结

该文章来自阿里巴巴技术协会(ATA)精选集

背景

我们在开发过程中,大部分是java开发, 而在文本处理过程中,主要就是脚本进行开发。 java开发的特点就是我们可以很早地进行TDDL, METAQ 等等地对接; 而脚本开发的特点就是在进行批处理的时候非常方便。 前阵子我遇到这么一个需求场景: 对抓取的数据进行打包, 后来又遇到我要通过脚本进行抓取,比如nodejs下基于phantomjs的casperjs爬虫。

解决方法

对于第一个问题:java抓取,并且把结果打包。 
那么比较直接的做法就是,java接收各种消息(db,metaq等等),然后借助于jstorm集群进行调度和抓取。 最后把抓取的结果保存到一个文件中,并且通过调用shell打包, 回传。 也许有同学会问, 为什么不直接把java调用odps直接保存文件,答案是,我们的集群不是hz集群,直接上传odps速度很有问题,因此先打包比较合适。(这里不纠结设计了,我们回到正题)

java调用shell的方法

通过ProcessBuilder进行调度

这种方法比较直观,而且参数的设置也比较方便, 比如我在实践中的代码(我隐藏了部分业务代码):

ProcessBuilder pb = new ProcessBuilder("./" + RUNNING_SHELL_FILE, param1,
                                               param2, param3);
        pb.directory(new File(SHELL_FILE_DIR));
        int runningStatus = 0;
        String s = null;
        try {
            Process p = pb.start();
            try {
                runningStatus = p.waitFor();
            } catch (InterruptedException e) {
            }

        } catch (IOException e) {
        }
        if (runningStatus != 0) {
        }
        return;

这里有必要解释一下几个参数:
RUNNING_SHELL_FILE:要运行的脚本
SHELL_FILE_DIR:要运行的脚本所在的目录; 当然你也可以把要运行的脚本写成全路径。
runningStatus:运行状态,0标识正常。 详细可以看java文档。
param1, param2, param3:可以在RUNNING_SHELL_FILE脚本中直接通过1,2,$3分别拿到的参数。

直接通过系统Runtime执行shell

这个方法比较暴力,也比较常用, 代码如下:

p = Runtime.getRuntime().exec(SHELL_FILE_DIR + RUNNING_SHELL_FILE + " "+param1+" "+param2+" "+param3);
p.waitFor();

我们发现,通过Runtime的方式并没有builder那么方便,特别是参数方面,必须自己加空格分开,因为exec会把整个字符串作为shell运行。

可能存在的问题以及解决方法

如果你觉得通过上面就能满足你的需求,那么可能是要碰壁了。你会遇到以下情况。

没权限运行

这个情况我们团队的朱东方就遇到了, 在做DTS迁移的过程中,要执行包里面的shell脚本, 解压出来了之后,发现执行不了。 那么就按照上面的方法授权吧

ProcessBuilder builder = new ProcessBuilder("/bin/chmod", "755", tempFile.getPath());
            Process process = builder.start();
            int rc = process.waitFor();

java进行一直等待shell返回

这个问题估计更加经常遇到。 原因是, shell脚本中有echo或者print输出, 导致缓冲区被用完了! 为了避免这种情况, 一定要把缓冲区读一下, 好处就是,可以对shell的具体运行状态进行log出来。 比如上面我的例子中我会变成:

ProcessBuilder pb = new ProcessBuilder("./" + RUNNING_SHELL_FILE, keyword.trim(),
                                               taskId.toString(), fileName);
        pb.directory(new File(CASPERJS_FILE_DIR));
        int runningStatus = 0;
        String s = null;
        try {
            Process p = pb.start();
            BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
            BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            while ((s = stdInput.readLine()) != null) {
                LOG.error(s);
            }
            while ((s = stdError.readLine()) != null) {
                LOG.error(s);
            }
            try {
                runningStatus = p.waitFor();
            } catch (InterruptedException e) {
            }

记得在start()之后, waitFor()之前把缓冲区读出来打log, 就可以看到你的shell为什么会没有按照预期运行。 这个还有一个好处是,可以读shell里面输出的结果, 方便java代码进一步操作。

也许你还会遇到这个问题,明明手工可以运行的命令,java调用的shell中某一些命令居然不能执行,报错:命令不存在!

比如我在使用casperjs的时候,手工去执行shell明明是可以执行的,但是java调用的时候,发现总是出错。 通过读取缓冲区就能发现错误日志了。 我发现即便自己把安装的casperjs的bin已经加入了path中(/etc/profile, 各种bashrc中)还不够。 比如:

export NODE_HOME="/home/admin/node"
export CASPERJS_HOME="/home/admin/casperjs"
export PHANTOMJS_HOME="/home/admin/phantomjs"
export PATH=$PATH:$JAVA_HOME/bin:/root/bin:$NODE_HOME/bin:$CASPERJS_HOME/bin:$PHANTOMJS_HOME/bin

原来是因为java在调用shell的时候,默认用的是系统的/bin/下的指令。特别是你用root权限运行的时候。 这时候,你要在/bin下加软链了。针对我上面的例子,就要在/bin下加软链:

ln -s /home/admin/casperjs/bin/casperjs casperjs;
ln -s /home/admin/node/bin/node node;
ln -s /home/admin/phantomjs/bin/phantomjs phantomjs;

这样,问题就可以解决了。

如果是通过java调用shell进行打包,那么要注意路径的问题了

因为shell里面tar的压缩和解压可不能直接写:

tar -zcf /home/admin/data/result.tar.gz /home/admin/data/result

直接给你报错,因为tar的压缩源必须到路径下面, 因此可以写成

tar -zcf /home/admin/data/result.tar.gz -C /home/admin/data/ result

如果我的shell是在jar包中怎么办?

答案是:解压出来。再按照上面指示进行操作。
(1)找到路径

String jarPath = findClassJarPath(ClassLoaderUtil.class);
        JarFile topLevelJarFile = null;
        try {
            topLevelJarFile = new JarFile(jarPath);
            Enumeration<JarEntry> entries = topLevelJarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (!entry.isDirectory() && entry.getName().endsWith(".sh")) {
                    对你的shell文件进行处理
                }
            }

对文件处理的方法就简单了,直接touch一个临时文件,然后把数据流写入,代码:

FileUtils.touch(tempjline);
tempjline.deleteOnExit();
FileOutputStream fos = new FileOutputStream(tempjline);
IOUtils.copy(ClassLoaderUtil.class.getResourceAsStream(r), fos);
fos.close();

有这个这个东东,相信大家会减少踩坑,而且大胆地使用java和脚本之间的交互吧。 java可以调用shell,那么shell再调用其他就方便了。 记得一点, 不要过度地依赖缓冲区进行线程之间的通信。原因自己去学习吧。

希望这个能帮到大家。

时间: 2024-10-31 08:54:32

怎么通过java去调用并执行shell脚本以及问题总结的相关文章

tomcat-JAVA连接远程服务器执行shell脚本启动TOMCAT权限问题

问题描述 JAVA连接远程服务器执行shell脚本启动TOMCAT权限问题 JAVA本地连接远程linux服务器执行shell脚本提示:Neither the JAVA_HOME nor the JRE_HOME environment variable is defined At least one of these environment variable is needed to run this program 注:脚本在服务器可以运行,连接用的也是root账户,是不是权限问题,还是其他

怎样在java代码中调用执行shell脚本呀

问题描述 遇到个问题   在本地压缩服务器上的xml文件 我就想编写shell教本 脚本内容是链接服务器 找到待压缩文件 压缩文件  说实话 我不知道这样是否可行  试试  但我不知道怎样在java代码中 调用执行shell脚本  谁能指点指点  求教...  问题补充:首先谢谢各位朋友的回答  在补充个小问题 <br />能在调用shell脚本时 同时给shell脚本传参数吗  不止一个 能这样写吗     <br />Runtime.getRuntime().exec(&quo

运维经验分享(六)-- 深究crontab不能正确执行Shell脚本的问题(二)

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://dgd2010.blog.51cto.com/1539422/1677211 运维经验分享作为一个专题,目前共7篇文章 <运维经验分享(一)-- Linux Shell之ChatterServer服务控制脚本> <运维经验分享(二)-- Linux Shell之ChatterServer服务控制脚本二次优化> <运维经验分享(三)-- 解决Ubuntu下cro

linux中php使用root权限执行shell脚本

今天晚上:19:30分,这个困扰了我好久的问题终于解决,其中的原理也终于弄清楚了,总之是利用sudo来赋予Apache的用户root的执行权限,下面记录一下: 利用php利用root权限执行shell脚本必须进行以下几个步骤:(所有步骤都是我亲自实验,若有不妥可指出,谢谢!) 1. 确定一下你的Apache的执行用户是谁.注:不一定就是nobody,我自行安装的httpd,我的Apache的用户就是daemon 2. 利用visudo为你的Apache执行用户赋予root执行权限,当然还有设置无

Linux中执行shell脚本的4种方法总结_linux shell

bash shell 脚本的方法有多种,现在作个小结.假设我们编写好的shell脚本的文件名为hello.sh,文件位置在/data/shell目录中并已有执行权限. 方法一:切换到shell脚本所在的目录(此时,称为工作目录)执行shell脚本: 复制代码 代码如下: cd /data/shell ./hello.sh ./的意思是说在当前的工作目录下执行hello.sh.如果不加上./,bash可能会响应找到不到hello.sh的错误信息.因为目前的工作目录(/data/shell)可能不在

执行Shell脚本的4种方法及区别介绍

  这篇文章主要介绍了执行Shell脚本的4种方法及区别介绍,本文讲解了相对路径方式.绝对路径方式.bash命令调用.相对或绝对方式以及说下几种方式的区别,需要的朋友可以参考下 执行shell脚本有以下几种方式 1.相对路径方式,需先cd到脚本路径下 代码如下: [root@banking tmp]# cd /tmp [root@banking tmp]# ./ceshi.sh 脚本执行成功 2.绝对路径方式 代码如下: [root@banking tmp]# /tmp/ceshi.sh 脚本执

PHP执行shell脚本运行程序不产生core文件的方法

发现一直不产生core文件,但是手动运行脚本的时候就会产生core文件. 经过朋友指导,原来是脚本执行环境导致的问题: 脚本中加入ulimit -a >> 1.log 打印如下: core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 127364 max

linux下的shell命令的编写,以及java如何调用linux的shell命令(java如何获取linux上的网卡的ip信息)

程序员都很懒,你懂的! 最近在开发中,需要用到服务器的ip和mac信息.但是服务器是架设在linux系统上的,对于多网口,在获取ip时就产生了很大的问题.下面是在windows系统上,java获取本地ip的方法.贴代码: package com.herman.test; import java.net.InetAddress; /** * @see 获取计算机ip * @author Herman.Xiong * @date 2014年5月16日 09:35:38 */ public class

防止同一个Java应用重复启动的shell脚本

通常,一个Java的后台应用程序,在Linux的终端可以启动多个,这样造成资源浪费,甚至相互操作竞争资源导致问题.这里通过也shell脚本启动应用,在shell脚本中做了些限制.就可以巧妙避免重复启动的问题了. 比如应用的目录结构为: app bin(存放启动脚本startup.sh和class文件.配置文件等.) lib(存放引用的库) 假设应用的类名为:mypack.MyAppMain startup.sh #!/bin/sh programdir="." program=&quo