1. 從一個問題開始
首先要從項目中遇到的一個問題說起。編寫一個python文件test.py,文件test.py內容如下:
#! /usr/bin/python
....
如果在命令行方式執(zhí)行test.py的方式是:
test.py -in inputfile -out outputfile;或python test.py -in inputfile -out outputfile;
但是因為需要,用exec函數(這里使用execl)去調用這個python文件。在項目中是這樣寫的:
execl(”test.py”,”-in”,”inputfile”,”-out”,”outputfile”,(char*)0);
但執(zhí)行結果并不是預想的test.py執(zhí)行,而是啟動了python交互程序,不知道是什么原因。因為一直以為如果寫一個C程序,比如main。那么在命令行輸入:main arg1 arg2執(zhí)行的效果和execl(”main”,”arg1”,”arg2”,(char*)0)的效果應該是一樣的。
當然同時伴隨我有另一個問題:
execl(“usr/bin/python”,”test.py”,(char*)0);和輸入命令”/usr/bin/python test.py”有什么區(qū)別?
為了回答些問題,自己通過再反復看apue和做實驗測試,終于一點一點明白了,下面就來一點一點的分析。
2. 命令行執(zhí)行程序和exec執(zhí)行程序的區(qū)別
首先我們來分析一下在命令行執(zhí)行一個程序和通過exec函數執(zhí)行程序有什么區(qū)別,或者說需要注意的地方(一下所有編寫的文件都在/mnt/hgfs/VWShared/目錄下)。
編寫程序foo.c如下,并編譯為可執(zhí)行文件foo。它打印參數列表(argv)的所有參數.
l foo.c
#include
int
main(int argc,char* argv[])
{
int i;
for(i=0;i
printf("argv[%d]: %sn",i,argv[i]);
exit(0);
}
再編寫main.c如下,將其編譯為可執(zhí)行文件main,它使用execl調用foo。
l main.c
#include
#include
int main(int argc,char* argv[])
{
int n=0;
if( (n=execl("/mnt/hgfs/VWShared/foo",(char*)0))==-1 )
{
perror("execl error");
exit(0);
}
exit(1);
}
直接在命令行下運行foo,結果如圖1:
圖1
運行main(通過execl運行foo)結果如圖2:
圖2
可以看出直接在命令行運行foo,則”./foo”被當做argv[0],但是通過exec運行foo發(fā)現并沒有參數傳入foo(程序沒有任何輸出),也就是說argc值為0。這是什么原因呢?我們知道argv存放的是傳遞給main函數的命令行參數,當在命令行鍵入”./foo”時,唯一的命令行參數”./foo”就被傳入給main的argv了。所以直接在命令行運行foo就打印出唯一的參數”./foo”。
那么execl的情況呢?首先看一下execl的原型:
int execl(const char* pathname,const char* arg0,.../*(char*)0*/);
注意到了吧,第一個參數是要執(zhí)行的程序名,第二個參數才是要傳入待執(zhí)行程序的第一個參數,而上述main.c中沒有第二個參數(這里說的是execl的第二個參數),也就是沒有給foo傳遞任何參數,foo的參數表argv當然就是空了,或者說argc為0。
通過這個例子我們要有以下認識:
argv[0]不一定就是所執(zhí)行程序的名稱,確切的說它只是命令行的第一個參數,只是通常啟動程序是在命令行鍵入程序名稱啟動的,所以程序的名稱才成為argv[0]。但是也有情況argv[0]不是程序名稱的,如:
(1) 通過exec執(zhí)行時,argv[0]是什么要視exec的參數來定。
例如:我們將main中的execl語句改為:execl("/mnt/hgfs/VWShared/foo","xxxxx",(char*)0);
再運行main,效果如圖3:
圖3
可以看到argv[0]變?yōu)榱宋覀儌魅氲膮?rdquo;xxxxx”。
(2) 通過程序別名啟動時,argv[0]就是程序的別名。如我們給foo創(chuàng)建一個軟連接sfoo,然后執(zhí)行sfoo效果如圖4:
圖4
可以看出輸出的argv[0]是./sfoo 而不是./foo,再次證明argv[0]是什么和程序名稱無關,只是和傳入的命令行第一個參數有關。
補充:在創(chuàng)建上述軟連接過程中遇到了一點小問題,不妨也在這里寫下來:
【問題】
在編譯VMware下的Linux系統(tǒng)對從Windows中共享過來的文件,進行編譯的時候,遇到:ln: creating symbolic link XXXXXX : Operation not supported
【解決辦法】
出現這類問題,主要是由于在編譯的時候,要用ln去建立一些軟鏈接,而這些文件是從Windows中,通過VMWare虛擬機共享進Linux的,而雖然此種操作在Linux系統(tǒng)中很常見,但Windows不支持,所以,編譯會報錯。比較方便的解決辦法是先將文件考到linux的其他目錄,再在其他非共享目錄中創(chuàng)建軟連接。另外還有個解決辦法就是,在VMWare下的Linux中,建立Samba服務,然后新創(chuàng)建新samba用戶和文件夾,然后在windows中就可以訪問到該文件夾了。然后把在Linux中,從共享目錄拷貝到你所要共享的samba目錄中,這樣,也可以實現我們所要的文件共享。此時在去編譯這些代碼的時候,由于是在Linux系統(tǒng)中的,所以就OK了。
3. 解釋器文件和解釋器[!--empirenews.page--]
先解釋兩個概念;解釋器文件和解釋器。
l 解釋器文件:一種文本文件,開頭通常是:#! pathname [option-argument];比較常見的是#! /bin/bash,shell腳本和python腳本都屬于解釋器文件。
l 解釋器:解釋器文件第一行中pathname指定的程序,如bash。
3.1 解釋器文件的執(zhí)行
當執(zhí)行(exec)"解釋器"文件時,exec系統(tǒng)調用會識別這種文件,內核使調用exec函數的進程實際執(zhí)行的并不是該"解釋器文件",而是pathname指定的解釋器。
我們可以自己寫一個解釋器,如之前所寫的foo.c:
l foo.c
#include
int
main(int argc,char* argv[])
{
int i;
for(i=0;i
printf("argv[%d]: %sn",i,argv[i]);
exit(0);
}
編譯成為foo然后保存在/mnt/hgfs/VWShared/。
下面我們在自己寫一個”解釋器文件”——test:
l test
#!/mnt/hgfs/VWShared/foo
3.1.1 通過命令行執(zhí)行解釋器文件
直接在命令行中鍵入:”./test”,運行test,效果如圖5。
圖5
將test內容修改為:#!/mnt/hgfs/VWShared/foo argA argB,再次在命令行運行test,效果如圖6。
圖6
通過這兩個例子,可以看出命令行運行時當執(zhí)行文件是”解釋器文件”時,參數是如何傳遞給解釋器的:
(1) 通過執(zhí)行”解釋器文件”執(zhí)行解釋器,傳遞給解釋器的第一個參數是解釋器文件的pathname,即解釋器的路徑。
(2) “解釋器文件”中pathname后的可選參數(這里的argA,argB)如果存在的話會一起作為第二個參數傳遞給解釋器。
(3) “解釋器文件”名稱會作為下一個參數傳遞給解釋器。
3.1.2 通過execl執(zhí)行解釋器文件
接下來通過execl執(zhí)行解釋器文件test,修改main中的exec語句如下:
execl("/mnt/hgfs/VWShared/test","arg1",”arg2”,(char*)0));然后執(zhí)行main,效果如圖7。
圖7
從這個例子可以了解當執(zhí)行文件是解釋器文件時,內核如何處理exec函數的參數及解釋器文件第一行的可選參數。我們知道執(zhí)行解釋器文件實際是執(zhí)行解釋器,由解釋器去讀取解釋器文件中的語句執(zhí)行,而第一行的pathname以#開頭在執(zhí)行時會被當做注釋忽略。下面就讓我們分析一下最終傳入解釋器foo的參數都是什么。
(1) argv[0]是該解釋器文件的pathname;
(2) argv[1]是該解釋文件中的可選參數;
(3) argv[2]是解釋器文件本身名字;
(4) argv[3]是execl出入的第二個參數(第一個參數是arg1)。
那么問題出現了,我們傳入execl的arg1去哪里了呢?其實這就是exec執(zhí)行”解釋器文件”和執(zhí)行一般程序的不同之處:在執(zhí)行一般程序時,execl(const char* pathname,const char* arg0,...,(char*)0)中的arg0會被當做執(zhí)行程序(pathname)的第一個參數argv[0],而在執(zhí)行解釋器文件時,內核取execl調用中的pathname而非第一個參數(arg0)作為第一個參數傳遞給解釋器,因為一般而言,第一個參數arg0通常是解釋器文件的名字,而pathname包含了比arg0更多的信息(解釋器文件的完整路徑)。所以當execl執(zhí)行解釋器文件時第一個參數arg0是無效的。
為了說明這個問題,我們再舉一個例子,編寫python文件pyth.py如下:
l pyth.py:
#! /usr/bin/python
import sys
for i in range(0,len(sys.argv)):
print "argv[%d]: %s"%(i,sys.argv[i])
它的功能和foo一樣同樣是打印每個命令行參數。我們分別將main中的execl語句改為:
execl("/mnt/hgfs/VWShared/foo","arg1","arg2",(char*)0))和
execl("/mnt/hgfs/VWShared/pyth.py","arg1","arg2",(char*)0)),對比execl一般程序(foo)和解釋器文件(pyth.py)的效果如圖8、9。
圖8.execl("/mnt/hgfs/VWShared/foo","arg1","arg2",(char*)0))結果
圖9.execl("/mnt/hgfs/VWShared/pyth.py","arg1","arg2",(char*)0))結果
可以看出execl對于執(zhí)行普通文件和解釋器文件選取第一個參數是不同的。
3.2 execl執(zhí)行解釋器文件和命令行執(zhí)行解釋器文件的不同
我們上面已經看到execl("/mnt/hgfs/VWShared/pyth.py","arg1","arg2",(char*)0))的結果(圖9),下面我們試一下命令行方式:pyth.py arg1 arg2,結果圖10:
圖10
可以看到結果和通過execl執(zhí)行是有區(qū)別的,通過命令行執(zhí)行解釋器文件就像通過命令行執(zhí)行普通程序一樣,程序名稱作為第一個參數,命令行后面依次作為后續(xù)參數。正因為對于解釋器文件的execl方式和命令行方式執(zhí)行時選取第一個參數的方式不同,所以對于解釋器文件a.py:[!--empirenews.page--]
(1) 在命令行輸入:./a.py arg1 arg2;
(2) execl("./a.py","arg1","arg2",(char*)0));
(3) execl("./a.py",”xxx”,"arg1","arg2",(char*)0));
方式(1)和方式(2)不等價,因為方式(1)中arg1會被當做第二個參數傳遞給解釋器,而方式(2)中arg2會被當做第二個參數傳遞給解釋器。方式(1)和方式(3)是等價的。
對于普通文件foo:
(1) 在命令行輸入: ./foo arg1 arg2;
(2) execl("./foo","arg1","arg2",(char*)0))
方式(1)和方式(2)是等價的。
4. 回答開始的問題
為了達到命令行方式:test.py arg1 arg2的效果,使用execl("test.py","arg1","arg2",(char*)0))肯定是不行的,因為arg1會被忽略,提示缺少參數。正確的方式是:execl("test.py",”xxx”,"arg1","arg2",(char*)0)),這里”xxx”代表任意字符串,不過一般會使用解釋器文件名,即”test.py”。
為了達到命令行方式:python test.py arg1 arg2的效果,使用execl("python",”test.py”,"arg1","arg2",(char*)0))也是不行的,因為test.py會被忽略,arg1會被當做第一個參數傳給python解釋器。正確方式是:
execl(“python",”xxx”,”test.py”,"arg1","arg2",(char*)0)),這里”xxx”代表任意字符串,不過一般會使用解釋器文件名,即”test.py”。