Java程序去調(diào)用并執(zhí)行shell腳本及問題總結(jié)(推薦)
摘要: 該文章來自阿里巴巴技術(shù)協(xié)會(ATA)精選集 背景 我們在開發(fā)過程中,大部分是java開發(fā), 而在文本處理過程中,主要就是腳本進(jìn)行開發(fā)。 java開發(fā)的特點就是我們可以很早地進(jìn)行TDDL, METAQ 等等地對接; 而腳本開發(fā)的特點就是在進(jìn)行批處理的時候非常方便。
背景
我們在開發(fā)過程中,大部分是java開發(fā), 而在文本處理過程中,主要就是腳本進(jìn)行開發(fā)。 java開發(fā)的特點就是我們可以很早地進(jìn)行TDDL, METAQ 等等地對接; 而腳本開發(fā)的特點就是在進(jìn)行批處理的時候非常方便。 前陣子我遇到這么一個需求場景: 對抓取的數(shù)據(jù)進(jìn)行打包, 后來又遇到我要通過腳本進(jìn)行抓取,比如nodejs下基于phantomjs的casperjs爬蟲。
解決方法
對于第一個問題:java抓取,并且把結(jié)果打包。
那么比較直接的做法就是,java接收各種消息(db,metaq等等),然后借助于jstorm集群進(jìn)行調(diào)度和抓取。 最后把抓取的結(jié)果保存到一個文件中,并且通過調(diào)用shell打包, 回傳。 也許有同學(xué)會問, 為什么不直接把java調(diào)用odps直接保存文件,答案是,我們的集群不是hz集群,直接上傳odps速度很有問題,因此先打包比較合適。(這里不糾結(jié)設(shè)計了,我們回到正題)
java調(diào)用shell的方法
通過ProcessBuilder進(jìn)行調(diào)度
這種方法比較直觀,而且參數(shù)的設(shè)置也比較方便, 比如我在實踐中的代碼(我隱藏了部分業(yè)務(wù)代碼):
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;
這里有必要解釋一下幾個參數(shù):
RUNNING_SHELL_FILE:要運(yùn)行的腳本
SHELL_FILE_DIR:要運(yùn)行的腳本所在的目錄; 當(dāng)然你也可以把要運(yùn)行的腳本寫成全路徑。
runningStatus:運(yùn)行狀態(tài),0標(biāo)識正常。 詳細(xì)可以看java文檔。
param1, param2, param3:可以在RUNNING_SHELL_FILE腳本中直接通過1,2,$3分別拿到的參數(shù)。
直接通過系統(tǒng)Runtime執(zhí)行shell
這個方法比較暴力,也比較常用, 代碼如下:
p = Runtime.getRuntime().exec(SHELL_FILE_DIR + RUNNING_SHELL_FILE + " "+param1+" "+param2+" "+param3); p.waitFor();
我們發(fā)現(xiàn),通過Runtime的方式并沒有builder那么方便,特別是參數(shù)方面,必須自己加空格分開,因為exec會把整個字符串作為shell運(yùn)行。
可能存在的問題以及解決方法
如果你覺得通過上面就能滿足你的需求,那么可能是要碰壁了。你會遇到以下情況。
沒權(quán)限運(yùn)行
這個情況我們團(tuán)隊的朱東方就遇到了, 在做DTS遷移的過程中,要執(zhí)行包里面的shell腳本, 解壓出來了之后,發(fā)現(xiàn)執(zhí)行不了。 那么就按照上面的方法授權(quán)吧
ProcessBuilder builder = new ProcessBuilder("/bin/chmod", "755", tempFile.getPath()); Process process = builder.start(); int rc = process.waitFor();
java進(jìn)行一直等待shell返回
這個問題估計更加經(jīng)常遇到。 原因是, shell腳本中有echo或者print輸出, 導(dǎo)致緩沖區(qū)被用完了! 為了避免這種情況, 一定要把緩沖區(qū)讀一下, 好處就是,可以對shell的具體運(yùn)行狀態(tài)進(jìn)行l(wèi)og出來。 比如上面我的例子中我會變成:
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()之前把緩沖區(qū)讀出來打log, 就可以看到你的shell為什么會沒有按照預(yù)期運(yùn)行。 這個還有一個好處是,可以讀shell里面輸出的結(jié)果, 方便java代碼進(jìn)一步操作。
也許你還會遇到這個問題,明明手工可以運(yùn)行的命令,java調(diào)用的shell中某一些命令居然不能執(zhí)行,報錯:命令不存在!
比如我在使用casperjs的時候,手工去執(zhí)行shell明明是可以執(zhí)行的,但是java調(diào)用的時候,發(fā)現(xiàn)總是出錯。 通過讀取緩沖區(qū)就能發(fā)現(xiàn)錯誤日志了。 我發(fā)現(xiàn)即便自己把安裝的casperjs的bin已經(jīng)加入了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在調(diào)用shell的時候,默認(rèn)用的是系統(tǒng)的/bin/下的指令。特別是你用root權(quán)限運(yùn)行的時候。 這時候,你要在/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調(diào)用shell進(jìn)行打包,那么要注意路徑的問題了
因為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包中怎么辦?
答案是:解壓出來。再按照上面指示進(jìn)行操作。
(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文件進(jìn)行處理 } }
對文件處理的方法就簡單了,直接touch一個臨時文件,然后把數(shù)據(jù)流寫入,代碼:
FileUtils.touch(tempjline); tempjline.deleteOnExit(); FileOutputStream fos = new FileOutputStream(tempjline); IOUtils.copy(ClassLoaderUtil.class.getResourceAsStream(r), fos); fos.close();
有這個這個東東,相信大家會減少踩坑,而且大膽地使用java和腳本之間的交互吧。 java可以調(diào)用shell,那么shell再調(diào)用其他就方便了。 記得一點, 不要過度地依賴緩沖區(qū)進(jìn)行線程之間的通信。原因自己去學(xué)習(xí)吧。
總結(jié)
到此這篇關(guān)于Java程序去調(diào)用并執(zhí)行shell腳本及問題總結(jié)的文章就介紹到這了,更多相關(guān)java調(diào)用并執(zhí)行shell腳本內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java中如何執(zhí)行xshell命令
- java在linux本地執(zhí)行shell命令的實現(xiàn)方法
- Java JSch遠(yuǎn)程執(zhí)行Shell命令的方法
- Java中如何執(zhí)行多條shell/bat命令
- 基于Java實現(xiàn)ssh命令登錄主機(jī)執(zhí)行shell命令過程解析
- Java代碼執(zhí)行shell命令的實現(xiàn)
- java調(diào)用shell命令并獲取執(zhí)行結(jié)果的示例
- Shell執(zhí)行/調(diào)用Java/Jar程序例子的實例詳解
- java通過ssh連接服務(wù)器執(zhí)行shell命令詳解及實例
- Java執(zhí)行shell命令的實現(xiàn)
相關(guān)文章
IDEA 自帶的數(shù)據(jù)庫工具真的很牛(收藏版)
這篇文章主要介紹了IDEA 自帶的數(shù)據(jù)庫工具真的很牛(收藏版),本文以 IntelliJ IDEA/ Mac 版本作為演示,其他版本的應(yīng)該也差距不大,需要的朋友可以參考下2021-04-04SpringBatch從入門到精通之StepScope作用域和用法詳解
這篇文章主要介紹了SpringBatch從入門到精通之StepScope作用域和用法詳解,主要包括IOC容器中幾種bean的作用范圍以及可能遇到的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-05-05基于Gradle搭建Spring?5.3.13-release源碼閱讀環(huán)境的詳細(xì)流程
這篇文章主要介紹了基于Gradle搭建Spring?5.3.13-release源碼閱讀環(huán)境,首先安裝jdk、gradle等一系列必要操作,本文通過實例代碼相結(jié)合給大家講解的非常詳細(xì),需要的朋友可以參考下2022-04-04Spring Dao層@Repository與@Mapper的使用
這篇文章主要介紹了Spring Dao層@Repository與@Mapper的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02Java日常練習(xí)題,每天進(jìn)步一點點(30)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07SpringBoot集成Beetl后統(tǒng)一處理頁面異常的方法
這篇文章主要介紹了SpringBoot集成Beetl后統(tǒng)一處理頁面異常的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08