mac java調用終端 的一些問題解決方法

一開始在網上搜索了一輪,有些人說mac 沒有cmd,那麼java就不能代碼調用mac的終端,後來我稍微試了一些,貌似也好像有這麼一回事。

用最簡單的,cd +路徑,再ls輸出。發現ls輸出的文件列表根本就是根目錄,就是說前面的cd指令沒作用。。。

後來想了下,既然ls指令可以使用,其他一些應該也是可以用的,我轉移使用shell腳本,用java調用終端打開腳本。shell要先給權限chmod 777 路徑加名字全稱;

再把我想用的指令都寫到shell腳本中,剛剛ls的列表就是我cd到的目錄下了,ok,正常了。

gradle 指令,用來命令行編譯生成apk,一開始,我寫好的腳本是直接拖進終端就可以運行生成apk了,但是到了java調用終端使用腳本時,一面的無奈。。。沒反應,沒輸出。。。後來在網上搜索了很久,終於被我發現了一個不錯的思路https://yq.aliyun.com/articles/2362,這篇裏面寫了一個關鍵的問題,也許你還會遇到這個問題,明明手工可以運行的命令,java調用的shell中某一些命令居然不能執行,報錯:命令不存在!

他說了使用軟連接,對於剛接觸shell的我,呃,呵呵。。。但我發現我一直忘卻掉一邊的java錯誤輸出,一開始我只是做了調試輸出,但我不清楚那是兩回事,看了他那篇博客,才明白了,而且我一直把思路放在shell錯誤輸出,暈菜。後來發現我的問題是,運行到apk編譯的時候,就出現java端口衝突。。。我是使用idea編譯做後臺,由java後臺調用終端,自動生成apk的,而idea直接運行後臺,使用了jvn。難道jvn和java編譯apk還能衝突。。。後臺我一直開着,之前的直接終端調用腳本或者終端直接調用指令都可以生成apk,爲什麼到了java調用就衝突呢。。。後來我直接生成war放在Tomcat上運行,那麼就不需要佔着端口,查了一些活動監視器,端口空出來了,直接調用後臺,終於正常生成apk了。


下面隨便把剛纔的博客內容複製一些:

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();


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章