1. 程式人生 > >ubuntu-shell 簡單地單檔案編譯

ubuntu-shell 簡單地單檔案編譯

最近在學習OpenGL(OpenGL超級寶典和OpenGL程式設計指南),於是就照著書中的例子編寫,由於是單檔案,所以也沒必要為每個檔案寫一個makefile檔案或者cmake檔案,那直接在命令列敲好了

gcc Triangle.cpp -o Triangle -lGL -lGLU -lm

很簡單的功能,無非是編譯Triangle.cpp檔案,並且連結庫libGL.so 和libGLU.so。來吧,接著往下看書比著葫蘆畫瓢吧,一個兩個檔案可能還沒啥,但是多了之後就會發現,上面那句特別常用,而且更換的也只是原始檔的名字而已;另外一個問題就是原始檔編譯好的可執行檔案的命名問題,這裡我是把它命名為與原始檔同名並且去掉副檔名,那麼問題就來了,當我在命令列敲擊Tri時(Tri的字首只有Triangle.cpp 和Triangle),按下[tab]只會自動補全至Triangle,要是想編譯Triangle.cpp還需要加上一個"."(懶到不想多敲一個字元)。

有問題就解決好了:

首先先頭腦風暴一下我的需求,是編譯一個檔案,且目標檔案的名字是原始檔去掉副檔名,並且我想命令列的自動補齊不受目標檔案的影響。

然後分析上面的一行的輸入,一個原始檔,預設的編譯選項,追加的編譯選項。

最後,如果我寫好了這個shell指令碼,我希望使用下面這句來代替

./mymake -lm Triangle.cpp

那麼接下來就是如何編寫這個shell指令碼了,目標檔案可以放在一個資料夾裡面,如bin資料夾,因此,我需要檢測是否存在bin資料夾;呼叫該指令碼至少需要一個引數,並且該檔名為指令碼的最後一個引數,中間的則為編譯選項(可選)。

首先建立一個檔案

touch mymake.sh
chmod a+x mymake.sh

然後vim開啟

vim mymake.sh

接下來就是指令碼檔案的編寫了。

#!/bin/bash 
#author sky
#desc 編譯單一檔案
#eg ./mymake -lm hello.c 將編譯該檔案,如果沒有錯誤則生成hello並直接執行

#編譯選項
libs='-lGL -lGLU -lglut -lgltools -lGLEW'
#引數個數
len=$#

#保證至少一個引數
if [ ${len} -eq 0 ]; then
        echo "Please input a source filename(*.c/*.cpp)" && exit 1
fi

#獲取最後一個引數,必為檔名
filename=${!len}

#追加編譯選項
libs="${libs} ${@%${filename}}"

#去除該檔案的副檔名
target=${filename%.*}

#對應的檔案存在且為普通檔案時,編譯
if [ -e ${filename} ] && [ -f ${filename} ]; then
        #是否存在對應的bin資料夾
        test -e bin || mkdir bin 
        #嘗試編譯檔案
        gcc ${filename} -o "bin/${target}" ${libs} && ./bin/${target} && exit 0 || echo "complie error" && exit 1
else
        echo "file:${filename} not exist" && exit 1
fi

值得一提的就是獲取引數,可以使用$1 $2...、也可以使用${1} ${2}...,這些都需要保證中間為確切的數字,如果為變數的話就得使用${!param}的形式,比如i=1,${!i}和$1相同。

接著就是[email protected]的使用,[email protected]是所有引數,並且中間使用空格隔開,在預設情況下,$*的作用和[email protected]的作用是一致的(預設情況下,$*分隔符為空格),因此我在獲取到檔名後,又使用${@%${filename}}來剔除掉檔名。

最後則是在保證原始檔存在的情況下嘗試編譯,如果編譯無誤,則直接執行,否則則輸出"complie error"並返回1

----------------------------需求變更=》指令碼2.0---------------------------------

這幾天在看《linux程式設計》,裡面的程式大多使用了命令列引數,然後我就發現上面的指令碼檔案不再適用了,為什麼呢?

因為上面的shell檔案只是負責根據連結庫進行編譯檔案後直接執行,沒有獲取到額外引數,也就無法傳送命令列引數。

如果還是按照上面的思路進行更新的話,那麼還是自行在指令碼檔案中進行判斷;不過這樣還是存在問題的,那就是擴充套件性比較差,因此嘗試使用getopt命令進行修改上述檔案,關於這個命令,可以稍微看一下:

http://www.jxbh.cn/article/2096.html

這個連結講的比較仔細。

值得一提的是,linux有提供getopt函式的,允許在c/c++中使用此函式,用法大致與命令相同,此處不再贅述。

那麼現在的需求是:我需要一個指令碼檔案,能附加連結庫地編譯檔案,並且允許傳遞若干個引數給c程式,即

給int main(int argc, char** argv)中的argc和argv進行賦值。

比如 ./make.sh -lm -lGL -f 1.c 1 2 3 4

的意思就是編譯1.c的同時連結libm.so 和libGL.so,如果編譯成功,就在呼叫這個可執行檔案的同時傳遞1 2 3 4;換句話說,此時的argc = 4, argv = {"1", "2", "3" , "4"}。

更改的指令碼檔案如下:

#! /bin/bash
#./make.sh [連結庫名稱] -f filename [引數] 
#引數將傳遞給filename可執行檔案
#./make.sh -lm -f 1.c 1 2 3

#判斷對應的連結庫是否存在
function has_link()
{
	return 0
}

libs=
filename=
args=

#先處理連結庫
while [ -n "$1" ]
do
	case "$1" in
		#對應的檔案
		"-f") break ;;
		#要連結的庫
		*) 
		if has_link "$1" ; then
			libs="${libs} $1"
			shift
		fi
	esac
done

#處理後續引數
set --$(getopt "f:" "[email protected]")

while [ -n "$1" ]
do
	case "$1" in
		"-f")
			filename="$2"
			shift 2
			;;
		"--") shift ;;
		"?") 
			echo "Invalid parameter $1"
			shift;;
		*) 
			args="${args} $1"
			shift ;;
	esac
done

#去除該檔案的副檔名
target=${filename%.*}

#檢測對應的檔案是否存在
if [ -e ${filename} ] && [ -f ${filename} ];  then
	#是否存在對應的bin資料夾
	test -e bin || mkdir bin
	#嘗試編譯檔案 執行檔案
	if gcc ${filename} -o "bin/${target}" ${libs} ; then
		echo "compile success,now running"
		./bin/${target}  ${args}
		echo "program return $?"
		exit 1
	fi
else
	echo "file:${filename} not exist" && exit 1
fi

has_link函式用來判斷是否存在對應的庫,這裡預設存在,方便以後的擴充套件。

set命令的功能負責把後面的引數賦值給[email protected]、$*,即引數。

另外[email protected]和$*區別還是比較大的:其一是$* 受到IFS變數的影響,預設為空格;其二則是[email protected]返回的是一個數組,而$*則是以$IFS變數分割的字串。

c語言指定返回0表示執行成功的好處是可以使用若干個1~後的錯誤值,然後再根據不同的值賦予不同的含義,這點在linux上尤其明顯,比如上面的一個條件語句:

if gcc ${filename} -o "bin/${target}" ${libs} ; then
#...
fi

如果編譯檔案成功,則直接執行,這裡的判斷就是gcc命令返回的結果是否為0。