0 Linux下Java使用ProcessBuilder執行命令與直接Bash執行命令之間的不同(環境變數方面)
0 問題發生
xiaojietest.java
package tasks; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.Writer; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.SQLException; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.SystemUtils; import database.Tools; import util.FixPath; import util.StreamGobbler; public class xiaojietest { public static void main(String args[]) throws SQLException { try { String cmd="\""+"/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner"+ "\" \"" + "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"+"\""; System.out.println(cmd); String cmd1="bash"; //String cmd2="--help"; String cmd2="-c"; //String [] exec = {cmd1,cmd2}; //String [] exec = {"bash", "-c", "\""+"/home/xiaojie/Desktop/xiaojiework/BigCloneEval/commands/../sample/nicadRunner"+ " " + "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"+"\""}; //String [] exec = {"bash", "--help"}; //String [] exec = {"bash", "-c",cmd}; String [] exec = {cmd1,cmd2,cmd}; //String[] exec= {"ls"}; ProcessBuilder pb = new ProcessBuilder(exec); //pb.directory(new File("/home/xiaojie/Desktop/xiaojiework/BigCloneEval/commands/../sample/")); Process p = pb.start(); Map<String, String>env=pb.environment(); //xiaojie output environment Set<String> key=env.keySet(); for(Iterator<String>it=key.iterator();it.hasNext();) { String s=it.next(); System.out.println(s+":"+env.get(s)); } //new StreamGobbler(p.getErrorStream()).start(); String line = null; BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); Path output=Paths.get("/home/xiaojie/Desktop/xiaojiework/data_for_experiment/nicadOutPutFile/nicad.clones"); output = FixPath.getAbsolutePath(output); output = output.toAbsolutePath(); BufferedWriter out = new BufferedWriter(new FileWriter(output.toFile())); //System.out.println(br.read()); while((line = br.readLine()) != null) { System.out.println(line); line = line.trim(); if(!line.equals("")) { out.write(line + "\n"); } } int retval = p.waitFor(); br.close(); System.out.println("retval:"+retval); } catch (Exception e) { e.printStackTrace(System.err); } } }
上述程式碼期望通過Java程式執行如下指令碼
/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner
並且傳入引數:
/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5
nicadRunner的指令碼內容是:
#!/bin/bash # This tool runner works with the myconfig.cfg nicad configuration file included # You will need to modify the hard-coded installation below before running # Test this out on one of the IJaDataset directories (such as 11/) to test and # see that clones are detected and output in the correct format for BigCloneEval # as specified in the readme. ulimit -s hard root=`dirname $1` dir=`basename $1` path=$root/$dir # Go to NiCad installation directory cd /home/xiaojie/Desktop/xiaojiework/NiCad-5.0/ # Execute NiCad, Suppress Output ./nicad5 functions java "$path" myconfig > /dev/null 2> /dev/null # Convert Detected Clones Into BigCloneEval Format java -jar Convert.jar ${path}_functions-blind-abstract-clones/${dir}_functions-blind-abstract-clones-0.30.xml 2> /dev/null #cat ${path}_functions-blind-abstract-clones/${dir}_functions-blind-abstract-clones-0.30.xml | sed 's$<source file="$$g' | sed 's$" startline="$,$g' | sed 's$" endline="$,$g' | sed 's$" pcid=.*"></source>$$g' | sed 's$<clone nlines=.*$$g' | sed 's$</clone>.*$$g' | sed 's$</clones>$$g' |sed 's$<clones>$$g' | sed 's$<cloneinfo.*$$g' | sed 's$<systeminfo.*$$g' | sed 's$<runinfo.*$$g' | sed '/^$/d' | paste -d ',' - - | sed "s#${path}/##g" | sed 's#/#,#g' # Cleanup rm -rf ${path}_functions-blind-abstract-clones > /dev/null 2> /dev/null rm ${path}_functions-blind-abstract.xml > /dev/null 2> /dev/null rm ${path}_functions-clones*.log > /dev/null 2> /dev/null rm ${path}_functions-blind.xml > /dev/null 2> /dev/null rm ${path}_functions.xml > /dev/null 2> /dev/null
ProcessBuilder啟動程序並執行,正常的返回值(通過程式碼中p.waitFor()返回)是0,其餘狀態都說明程序執行過程報錯。
針對"ls"、"bash --help"等使用上面程式執行,都無錯誤。
但是針對如下程序使用上述程式通過ProcessBuilder啟動程序執行卻一直報錯:
bash -c "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner" "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"
1 問題排查過程
1.1 bash -c的直接使用
首先,直接執行指令碼,傳入引數。沒有任何錯誤。
其次,加上bash –c以後就會出錯。
這是因為必須將"/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner" "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"作為整體傳遞給bash -c,而不是分開。所以如下修改即可:
1.2 通過ProcessBuilder啟動程序執行bash -c
問題1:返回127錯誤碼
String cmd="\""+"/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner"+ "\" \"" + "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"+"\""; System.out.println(cmd); String cmd1="bash"; //String cmd2="--help"; String cmd2="-c"; String [] exec = {cmd1,cmd2,cmd}; ProcessBuilder pb = new ProcessBuilder(exec);
第一,如果將cmd寫作
"\"\""+"/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner"+ "\" \"" + "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"+"\"\"";
將該字串列印以後會輸出:
""/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner" "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5""
從表面看,是bash -c能夠接受的引數,即是一個整體。
這個時候,bash -c 將“”引號內作為一個整體看待,而不是一個指令碼和一個引數,故而會提示127。
但是,對於ProcessBuilder而言,其接收該引數對其處理時,會將其當作最外層還有一層雙引號。就變成了
"""/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner" "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"""
會報出127錯誤。
shell的錯誤碼說明是:
因此,找不到nicadRunner指令碼的路徑。即Path不對。
問題2:返回1錯誤碼
我們將問題1修正以後,即cmd為
String cmd="\""+"/home/xiaojie/Desktop/xiaojiework/BigCloneEval/sample/nicadRunner"+ "\" \"" + "/home/xiaojie/Desktop/xiaojiework/BigCloneEval/ijadataset/bcb_reduced/5"+"\"";
此時,執行前述程式後,返回1錯誤碼,即通用錯誤。
說明nicadRunner指令碼運行了,但是沒有正確執行。
採取“逐步增加行”的策略。我們執行到./nicad5的那一行時出了錯誤。
#!/bin/bash # This tool runner works with the myconfig.cfg nicad configuration file included # You will need to modify the hard-coded installation below before running # Test this out on one of the IJaDataset directories (such as 11/) to test and # see that clones are detected and output in the correct format for BigCloneEval # as specified in the readme. ulimit -s hard root=`dirname $1` dir=`basename $1` path=$root/$dir # Go to NiCad installation directory cd /home/xiaojie/Desktop/xiaojiework/NiCad-5.0/ # Execute NiCad, Suppress Output ./nicad5 functions java "$path" myconfig > /dev/null 2> /dev/null
而直接在客戶端的命令列中用nicad命令執行,卻沒有錯誤。
為什麼?
在客戶端的命令列中執行nicad程式,命令列中的上下文的path是包括FreeTXL路徑的,所以命令列nicad沒問題。
但是,使用ProcessBuilder啟動bash -c 執行nicadRunner指令碼,指令碼中再呼叫nicad程式的時候,就找不到FreeTXL路徑了!
我的FreeTXL路徑設定的比較特殊,是在一個私人資料夾,並且寫入的path是~/.bashrc。FreeTXL是nicad工具的依賴包。我安裝的時候,相關的路徑資訊是:
Installing TXL for xiaojie only. Installing TXL commands into /home/xiaojie/bin Installing TXL library into /home/xiaojie/txl/lib Installing TXL manual entries into /home/xiaojie/txl/man/man1 Testing TXL
我在程式中新增程式碼:
Map<String, String>env=pb.environment(); //xiaojie output environment Set<String> key=env.keySet(); for(Iterator<String>it=key.iterator();it.hasNext();) { String s=it.next(); System.out.println(s+":"+env.get(s)); }
獲取了ProcessBuilder啟動的命令的上下文,然後檢視一下輸出
PATH:/home/xiaojie/Desktop/xiaojiework/jdk-8u191-linux-x64/jdk1.8.0_191/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
可以看到,/home/xiaojie/bin不在PATH路徑中。
我當時將這個路徑寫入了一個bashrc檔案,所以bash命令列中有該路徑!
但是,建立ProcessBuilder時,讀取的上下文,不是從bashrc中讀取的。
所以不一致,所以就會導致命令列和程式中的不一樣。
除錯程式,跟入pb.environment();,可以看到:
進一步跟入,發現:
該函式只有一個宣告。
初步判斷是java.lang.ProcessEnvironment.environ函式。java.lang.ProcessEnvironment是java的一個類,原始碼被隱藏。可以直接呼叫。
https://www.ibm.com/developerworks/cn/java/java-random-code-from-the-perspective-of-compilation/。該網頁中有Linux環境變數的讀取介紹,比較複雜。
https://www.cnblogs.com/sunilsun/p/6071124.html這裡是Linux環境變數的介紹。
裡面提到:
(1)~/.profile:【推薦】每個使用者都可使用該檔案輸入專用於自己使用的shell資訊,當用戶登入時,該檔案僅僅執行一次!預設情況下,他設定一些環境變數,執行使用者的.bashrc檔案。這裡是推薦放置個人設定的地方
(2)~/.bashrc:該檔案包含專用於你的bash shell的bash資訊,當登入時以及每次開啟新的shell時,該檔案被讀取。不推薦放到這兒,因為每開一個shell,這個檔案會讀取一次,效率肯定有影響。
所以,我將路徑改為寫入~/.profile。重啟,然後執行程式。解決。