在java程序中如何調(diào)用linux的命令?如何調(diào)用shell腳本呢?
創(chuàng)新互聯(lián)是一家專注于成都做網(wǎng)站、網(wǎng)站建設(shè)與策劃設(shè)計(jì),三山網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:三山等地區(qū)。三山做網(wǎng)站價(jià)格咨詢:18980820575
這里不得不提到j(luò)ava的process類了。
process這個(gè)類是一個(gè)抽象類,封裝了一個(gè)進(jìn)程(你在調(diào)用linux的命令或者shell腳本就是為了執(zhí)行一個(gè)在linux下執(zhí)行的程序,所以應(yīng)該使用process類)。
process類提供了執(zhí)行從進(jìn)程輸入,執(zhí)行輸出到進(jìn)程,等待進(jìn)程完成,檢查進(jìn)程的推出狀態(tài),以及shut down掉進(jìn)程。
至于詳細(xì)的process類的介紹放在以后介紹。
另外還要注意一個(gè)類:Runtime類,Runtime類是一個(gè)與JVM運(yùn)行時(shí)環(huán)境有關(guān)的類,這個(gè)類是Singleton的。
這里用到的Runtime.getRuntime()方法是取得當(dāng)前JVM的運(yùn)行環(huán)境,也是java中唯一可以得到運(yùn)行環(huán)境的方法。(另外,Runtime的大部分方法都是實(shí)例方法,也就是說(shuō)每次運(yùn)行調(diào)用的時(shí)候都需要調(diào)用到getRuntime方法)
下面說(shuō)說(shuō)Runtime的exec()方法,這里要注意的有一點(diǎn),就是public Process exec(String [] cmdArray, String [] envp);這個(gè)方法中cmdArray是一個(gè)執(zhí)行的命令和參數(shù)的字符串?dāng)?shù)組,數(shù)組的第一個(gè)元素是要執(zhí)行的命令往后依次都是命令的參數(shù),envp感覺(jué)應(yīng)該和C中的execve中的環(huán)境變量是一樣的,envp中使用的是name=value的方式。
下面說(shuō)一下,如何使用process來(lái)調(diào)用shell腳本
例如,我需要在linux下實(shí)行l(wèi)inux命令:sh test.sh,下面就是執(zhí)行test.sh命令的方法:
這個(gè)var參數(shù)就是日期這個(gè)201102包的名字。
String shpath="/test/test.sh"; //程序路徑
Process process =null;
String command1 = “chmod 777 ” + shpath;
process = Runtime.getRuntime().exec(command1);
process.waitFor();
String var="201102"; //參數(shù)
String command2 = “/bin/sh ” + shpath + ” ” + var;
Runtime.getRuntime().exec(command2).waitFor();
注意:
1
我為什么要使用 chmod 777命令呢?在有的機(jī)器上面,可能沒(méi)有設(shè)置權(quán)限問(wèn)題。這是你在linux下面執(zhí)行shell腳本需要注意的問(wèn)題。沒(méi)有的話,就需要添加權(quán)限,就用chmod 777,否則在執(zhí)行到Runtime.getRuntime().exec的時(shí)侯會(huì)出現(xiàn)Permission denied錯(cuò)誤。
2
waitFor()這個(gè)也是必不可缺的,如果你需要執(zhí)行多行命令的話,把waitFor()這個(gè)加上。
**
* 運(yùn)行shell腳本
* @param shell 需要運(yùn)行的shell腳本
*/
public static void execShell(String shell){
try {
Runtime rt = Runtime.getRuntime();
rt.exec(shell);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 運(yùn)行shell
*
* @param shStr
* 需要執(zhí)行的shell
* @return
* @throws IOException
*/
public static List runShell(String shStr) throws Exception {
ListString strList = new ArrayList();
Process process;
process = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",shStr},null,null);
InputStreamReader ir = new InputStreamReader(process
.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
String line;
process.waitFor();
while ((line = input.readLine()) != null){
strList.add(line);
}
return strList;
}
;?? 本例子使用了JDK 以上的javax script包 通過(guò)Invocable接口實(shí)現(xiàn)JAVA調(diào)用JS腳本里的方法 本例中調(diào)用分為內(nèi)部執(zhí)行腳本和外部執(zhí)行腳本 外部執(zhí)行腳本分為不帶參數(shù)調(diào)用和帶參數(shù)調(diào)用 主要采用invokeFunction方法調(diào)用 ??? ??? ObjectinvokeFunction(String name Object… args)??? ??? 用于調(diào)用腳本中定義的頂層程序和函數(shù) ??? ??? ObjectinvokeMethod(Object thiz String name Object… args)??? ??? 對(duì)以前的腳本執(zhí)行中已經(jīng)編譯的腳本對(duì)象(以 ScriptEngine 的狀態(tài)保持)調(diào)用某一方法 ??? ??? package de javase;??? ??? import javax script Invocable;??? ??? import javax script ScriptEngineManager;??? ??? import javax script ScriptEngine;??? ??? import java io File;??? ??? import java io Reader;??? ??? import java io FileReader;??? ??? public class AjavaInvokingFunction {??? ??? public static void main(String[] args) {??? ??? // 獲得一個(gè)JavaScript腳本引擎 也可以是ECMAScript腳本引擎??? ??? ScriptEngineManager manager = new ScriptEngineManager() ??? ??? ScriptEngine engine = manager getEngineByName( JavaScript ) ??? ??? String titlescript = function sayTitle() { ??? ??? + ?? println( 源碼實(shí)例 使用javax script調(diào)用JS腳本里的方法 ) ??? ??? + ?? println( ) ??? ??? + } ;??? ??? try {???
// 調(diào)用內(nèi)部腳本執(zhí)行 ??? ??? engine eval(titlescript) ??? ??? // 轉(zhuǎn)換為Invocable??? ??? Invocable invocableEngine = (Invocable) engine;??? ??? // 不帶參數(shù)調(diào)用sayTitle方法??? ??? invocableEngine invokeFunction( sayTitle ) ??? ??? // 調(diào)用外部腳本執(zhí)行 ??? ??? // 創(chuàng)建JS文件的File對(duì)象 并讀入流??? ??? File functionscript = new File( ajava js ) ??? ??? Reader reader = new FileReader(functionscript) ??? ??? // 開始執(zhí)行ajava js里的程序??? ??? engine eval(reader) ??? ??? // 不帶參數(shù)調(diào)用sayHello方法??? ??? invocableEngine invokeFunction( sayHello ) ??? ??? // 帶參數(shù)調(diào)用sayHello方法??? ??? invocableEngine invokeFunction( sayHello mark ) ??? ??? } catch (Exception e) {??? ??? e printStackTrace() ??? ??? }??? ??? }??? ??? }??? ??? ajava js文件 ??? ??? function sayHello(){??? ??? sayHello(null) ??? ??? }??? ??? function sayHello(name){??? ??? if(name==null){??? ??? println( 歡迎你!請(qǐng)注冊(cè)Ajava會(huì)員 ) ??? ??? }else{??? ??? println(name+ 獨(dú)立JAVA社區(qū)歡迎你 ) ??? ??? }??? ??? }??? ??? 運(yùn)行結(jié)果(控制臺(tái)) ??? ??? 源碼實(shí)例 使用javax script調(diào)用JS腳本里的方法 lishixinzhi/Article/program/Java/hx/201311/26436
在寫程序時(shí),有時(shí)需要在java程序中調(diào)用shell腳本,可以通過(guò)Runtime的exec方法來(lái)調(diào)用shell程序,運(yùn)行腳本。每個(gè)Java 應(yīng)用程序都有一個(gè)Runtime 類實(shí)例,使應(yīng)用程序能夠與其運(yùn)行的環(huán)境相連接。通過(guò)Runtime對(duì)象可以返回運(yùn)行環(huán)境的情況,包括CPU數(shù),虛擬機(jī)內(nèi)存大小等,并能夠通過(guò)exec方法調(diào)用執(zhí)行命令??梢酝ㄟ^(guò)getRuntime 方法獲取當(dāng)前Runtime實(shí)例。 public boolean ExeShell(){ Runtime rt = Runtime.getRuntime(); try { Process p = rt.exec(checkShellName); if(p.waitFor() != 0) return false; } catch (IOException e) { SysLog.error("沒(méi)有找到檢測(cè)腳本"); return false; } catch (InterruptedException e) { e.printStackTrace(); return false; } return true; } 其中p.waitFor()語(yǔ)句用來(lái)等待子進(jìn)程結(jié)束,其返回值為進(jìn)程結(jié)束退出碼。
對(duì)于第一個(gè)問(wèn)題:java抓取,并且把結(jié)果打包。那么比較直接的做法就是,java接收各種消息(db,metaq等等),然后借助于jstorm集群進(jìn)行調(diào)度和抓取。
最后把抓取的結(jié)果保存到一個(gè)文件中,并且通過(guò)調(diào)用shell打包, 回傳。 也許有同學(xué)會(huì)問(wèn),
為什么不直接把java調(diào)用odps直接保存文件,答案是,我們的集群不是hz集群,直接上傳odps速度很有問(wèn)題,因此先打包比較合適。(這里不糾結(jié)設(shè)計(jì)了,我們回到正題)
java調(diào)用shell的方法
通過(guò)ProcessBuilder進(jìn)行調(diào)度
這種方法比較直觀,而且參數(shù)的設(shè)置也比較方便, 比如我在實(shí)踐中的代碼(我隱藏了部分業(yè)務(wù)代碼):
ProcessBuilderpb = 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;
這里有必要解釋一下幾個(gè)參數(shù):
RUNNING_SHELL_FILE:要運(yùn)行的腳本
SHELL_FILE_DIR:要運(yùn)行的腳本所在的目錄; 當(dāng)然你也可以把要運(yùn)行的腳本寫成全路徑。
runningStatus:運(yùn)行狀態(tài),0標(biāo)識(shí)正常。 詳細(xì)可以看java文檔。
param1, param2, param3:可以在RUNNING_SHELL_FILE腳本中直接通過(guò)1,2,$3分別拿到的參數(shù)。
直接通過(guò)系統(tǒng)Runtime執(zhí)行shell
這個(gè)方法比較暴力,也比較常用, 代碼如下:
p = Runtime.getRuntime().exec(SHELL_FILE_DIR + RUNNING_SHELL_FILE + " "+param1+" "+param2+" "+param3);
p.waitFor();
我們發(fā)現(xiàn),通過(guò)Runtime的方式并沒(méi)有builder那么方便,特別是參數(shù)方面,必須自己加空格分開,因?yàn)閑xec會(huì)把整個(gè)字符串作為shell運(yùn)行。
可能存在的問(wèn)題以及解決方法
如果你覺(jué)得通過(guò)上面就能滿足你的需求,那么可能是要碰壁了。你會(huì)遇到以下情況。
沒(méi)權(quán)限運(yùn)行
這個(gè)情況我們團(tuán)隊(duì)的朱東方就遇到了, 在做DTS遷移的過(guò)程中,要執(zhí)行包里面的shell腳本, 解壓出來(lái)了之后,發(fā)現(xiàn)執(zhí)行不了。 那么就按照上面的方法授權(quán)吧
java進(jìn)行一直等待shell返回
這個(gè)問(wèn)題估計(jì)更加經(jīng)常遇到。 原因是, shell腳本中有echo或者print輸出, 導(dǎo)致緩沖區(qū)被用完了! 為了避免這種情況, 一定要把緩沖區(qū)讀一下, 好處就是,可以對(duì)shell的具體運(yùn)行狀態(tài)進(jìn)行l(wèi)og出來(lái)。 比如上面我的例子中我會(huì)變成:
ProcessBuilderpb = 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();
BufferedReaderstdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
BufferedReaderstdError = 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()之前把緩沖區(qū)讀出來(lái)打log, 就可以看到你的shell為什么會(huì)沒(méi)有按照預(yù)期運(yùn)行。 這個(gè)還有一個(gè)好處是,可以讀shell里面輸出的結(jié)果, 方便java代碼進(jìn)一步操作。
也許你還會(huì)遇到這個(gè)問(wèn)題,明明手工可以運(yùn)行的命令,java調(diào)用的shell中某一些命令居然不能執(zhí)行,報(bào)錯(cuò):命令不存在!
比如我在使用casperjs的時(shí)候,手工去執(zhí)行shell明明是可以執(zhí)行的,但是java調(diào)用的時(shí)候,發(fā)現(xiàn)總是出錯(cuò)。
通過(guò)讀取緩沖區(qū)就能發(fā)現(xiàn)錯(cuò)誤日志了。 我發(fā)現(xiàn)即便自己把安裝的casperjs的bin已經(jīng)加入了path中(/etc/profile,
各種bashrc中)還不夠。 比如:
exportNODE_HOME="/home/admin/node"
exportCASPERJS_HOME="/home/admin/casperjs"
exportPHANTOMJS_HOME="/home/admin/phantomjs"
exportPATH=$PATH:$JAVA_HOME/bin:/root/bin:$NODE_HOME/bin:$CASPERJS_HOME/bin:$PHANTOMJS_HOME/bin
原來(lái)是因?yàn)閖ava在調(diào)用shell的時(shí)候,默認(rèn)用的是系統(tǒng)的/bin/下的指令。特別是你用root權(quán)限運(yùn)行的時(shí)候。 這時(shí)候,你要在/bin下加軟鏈了。針對(duì)我上面的例子,就要在/bin下加軟鏈:
ln -s /home/admin/casperjs/bin/casperjscasperjs;
ln -s /home/admin/node/bin/nodenode;
ln -s /home/admin/phantomjs/bin/phantomjsphantomjs;
這樣,問(wèn)題就可以解決了。
如果是通過(guò)java調(diào)用shell進(jìn)行打包,那么要注意路徑的問(wèn)題了
因?yàn)閟hell里面tar的壓縮和解壓可不能直接寫:
tar -zcf /home/admin/data/result.tar.gz /home/admin/data/result
直接給你報(bào)錯(cuò),因?yàn)閠ar的壓縮源必須到路徑下面, 因此可以寫成
tar -zcf /home/admin/data/result.tar.gz -C /home/admin/data/ result
如果我的shell是在jar包中怎么辦?
答案是:解壓出來(lái)。再按照上面指示進(jìn)行操作。(1)找到路徑
String jarPath = findClassJarPath(ClassLoaderUtil.class);
JarFiletopLevelJarFile = null;
try {
topLevelJarFile = new JarFile(jarPath);
EnumerationJarEntry entries = topLevelJarFile.entries();
while (entries.hasMoreElements()) {
JarEntryentry = entries.nextElement();
if (!entry.isDirectory() entry.getName().endsWith(".sh")) {
對(duì)你的shell文件進(jìn)行處理
}
}
對(duì)文件處理的方法就簡(jiǎn)單了,直接touch一個(gè)臨時(shí)文件,然后把數(shù)據(jù)流寫入,代碼:
FileUtils.touch(tempjline);
tempjline.deleteOnExit();
FileOutputStreamfos = new FileOutputStream(tempjline);
IOUtils.copy(ClassLoaderUtil.class.getResourceAsStream(r), fos);
fos.close();