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。