c語言中調用shell腳本返回值問題分析

c語言中調用shell腳本


C程序調用shell腳本共有三種方式:system()、popen()、exec系列函數


1)system(shell命令或shell腳本路徑);
    
    執行過程:system()會調用fork()產生子進程,由子進程來調用/bin/sh-c string來執行參數string字符串所代表的命令,此命令執行完後隨即返回原調用的進程。在調用system()期間SIGCHLD 信號會被暫時擱置,SIGINT和SIGQUIT 信號則會被忽略。
    
    返回值:如果system()在調用/bin/sh時失敗則返回127,其他失敗原因返回-1。若參數string爲空指針(NULL),則返回非零值。如果 system()調用成功則最後會返回執行shell命令後的返回值,但是此返回值也有可能爲system()調用/bin/sh失敗所返回的127,因此最好能再檢查errno 來確認執行成功。


    注意:在編寫具有SUID/SGID權限的程序時最好不要使用system(),system()會繼承環境變量,通過環境變量可能會造成系統安全的問題。


例:在~/myprogram/目錄下有shell腳本test.sh,內容爲


#!bin/bash
#test.sh
echo $HOME


在該目錄下新建一個c文件systemtest.c,內容爲:


#include<stdlib.h>
/*This program is used to test function system*/


main()
{
  system("~/myprogram/test.sh");
}


執行結果如下:


xiakeyou@ubuntu:~/myprogram$ gcc systemtest.c -o systemtest
xiakeyou@ubuntu:~/myprogram$ ./systemtest 
/home/d/e/xiakeyou
xiakeyou@ubuntu:~/myprogram$


2)popen(char *command,char *type)    


    執行過程:popen()會調用fork()產生子進程,然後從子進程中調用/bin/sh -c來執行參數command的指令。參數type可使用“r”代表讀取,“w”代表寫入。依照此type值,popen()會建立管道連到子進程的標準輸出設備或標準輸入設備,然後返回一個文件指針。隨後進程便可利用此文件指針來讀取子進程的輸出設備或是寫入到子進程的標準輸入設備中。此外,所有使用文件指針(FILE*)操作的函數也都可以使用,除了fclose()以外。


    返回值:若成功則返回文件指針,否則返回NULL,錯誤原因存於errno中。


    注意:在編寫具SUID/SGID權限的程序時請儘量避免使用popen(),popen()會繼承環境變量,通過環境變量可能會造成系統安全的問題。
例:C程序popentest.c內容如下:


    #include<stdio.h>
    main()
    {
        FILE * fp;
        charbuffer[80];
        fp=popen(“~/myprogram/test.sh”,”r”);
        fgets(buffer,sizeof(buffer),fp);
        printf(“%s”,buffer);
        pclose(fp);
    }


執行結果如下:


xiakeyou@ubuntu:~/myprogram$ vim popentest.c
xiakeyou@ubuntu:~/myprogram$ gcc popentest.c -o popentest
xiakeyou@ubuntu:~/myprogram$ ./popentest
/home/d/e/xiakeyou
xiakeyou@ubuntu:~/myprogram$ 


linux exec的用法


說是exec系統調用,實際上在Linux中,並不存在一個exec()的函數形式,exec指的是一組函數,一共有6個,分別是:
#include <unistd.h>


extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);


int execve(const char *path, char *const argv[], char *const envp[]);
 
其中只有execve是真正意義上的系統調用,其它都是在此基礎上經過包裝的庫函數。


exec函數族的作用是根據指定的文件名找到可執行文件,並用它來取代調用進程的內容,換句話說,就是在調用進程內部執行一個可執行文件。這裏的可執行文件既可以是二進制文件,也可以是任何Linux下可執行的腳本文件。


與一般情況不同,exec函數族的函數執行成功後不會返回,因爲調用進程的實體,包括代碼段,數據段和堆棧等都已經被新的內容取代,只留下進程ID等一些表面上的信息仍保持原樣,頗有些神似"三十六計"中的"金蟬脫殼"。看上去還是舊的軀殼,卻已經注入了新的靈魂。只有調用失敗了,它們纔會返回一個-1,從原程序的調用點接着往下執行。
 
現在我們應該明白了,Linux下是如何執行新程序的,每當有進程認爲自己不能爲系統和擁護做出任何貢獻了,他就可以發揮最後一點餘熱,調用任何一個exec,讓自己以新的面貌重生;或者,更普遍的情況是,如果一個進程想執行另一個程序,它就可以fork出一個新進程,然後調用任何一個exec,這樣看起來就好像通過執行應用程序而產生了一個新進程一樣。


事實上第二種情況被應用得如此普遍,以至於Linux專門爲其作了優化,我們已經知道,fork會將調用進程的所有內容原封不動的拷貝到新產生的子進程中去,這些拷貝的動作很消耗時間,而如果fork完之後我們馬上就調用exec,這些辛辛苦苦拷貝來的東西又會被立刻抹掉,這看起來非常不划算,於是人們設計了一種"寫時拷貝(copy-on-write)"技術,使得fork結束後並不立刻複製父進程的內容,而是到了真正實用的時候才複製,這樣如果下一條語句是exec,它就不會白白作無用功了,也就提高了效率。


返回值
如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。
大家在平時的編程中,如果用到了exec函數族,一定記得要加錯誤判斷語句。因爲與其他系統調用比起來,exec很容易受傷,被執行文件的位置,權限等很多因素都能導致該調用的失敗。最常見的錯誤是:
1.找不到文件或路徑,此時errno被設置爲ENOENT; 
2.數組argv和envp忘記用NULL結束,此時errno被設置爲EFAULT; 
3.沒有對要執行文件的運行權限,此時errno被設置爲EACCES。


l表示以參數列表的形式調用
v表示以參數數組的方式調用
e表示可傳遞環境變量
p表示PATH中搜索執行的文件,如果給出的不是絕對路徑就會去PATH搜索相應名字的文件,如PATH沒有設置,則會默認在/bin,/usr/bin下搜索。
另:調用時參數必須以NULL結束。原進程打開的文件描述符是不會在exec中關閉的,除非用fcntl設置它們的“執行時關閉標誌(close on exec)”而原進程打開的目錄流都將在新進程中關閉。
例子:
#include <unistd.h>
int main(int argc, char *argv[])
{
char *envp[]={"PATH=/tmp", "USER=lei", "STATUS=testing", NULL};
char *argv_execv[]={"echo", "excuted by execv", NULL};
char *argv_execvp[]={"echo", "executed by execvp", NULL};
char *argv_execve[]={"env", NULL};
if(fork()==0) {
if(execl("/bin/echo", "echo", "executed by execl", NULL)<0)
perror("Err on execl");
}
if(fork()==0) {
if(execlp("echo", "echo", "executed by execlp", NULL)<0)
perror("Err on execlp");
}
if(fork()==0) {
if(execle("/usr/bin/env", "env", NULL, envp)<0)
perror("Err on execle");
}
if(fork()==0) {
if(execv("/bin/echo", argv_execv)<0)
perror("Err on execv");
}
if(fork()==0) {
if(execvp("echo", argv_execvp)<0)
perror("Err on execvp");
}
if(fork()==0) {
if(execve("/usr/bin/env", argv_execve, envp)<0)
perror("Err on execve");
}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章