1. 程式人生 > >0 Linux下Java使用ProcessBuilder執行命令與直接Bash執行命令之間的不同(環境變數方面)

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。重啟,然後執行程式。解決。