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。重啟,然后運行程序。解決。

 

 

 

 



 

 

 
       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM