1. 程式人生 > >C程式呼叫shell指令碼共有三種方式:system()、popen()、exec系列函式

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
/*This program is used to test function system*/


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


執行結果如下:


[email protected]:~/myprogram$ gcc systemtest.c -o systemtest

[email protected]:~/myprogram$ ./systemtest 
/home/d/e/xiakeyou
[email protected]:~/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
    main()
    {
        FILE * fp;
        charbuffer[80];
        fp=popen(“~/myprogram/test.sh”,”r”);
        fgets(buffer,sizeof(buffer),fp);
        printf(“%s”,buffer);
        pclose(fp);
    }


執行結果如下:


[email protected]:~/myprogram$ vim popentest.c
[email protected]:~/myprogram$ gcc popentest.c -o popentest
[email protected]:~/myprogram$ ./popentest
/home/d/e/xiakeyou

popen函式執行命令後,返回一個指向該命令輸出的檔案控制代碼,接下來就可以用fgets等檔案操作函式去讀取輸出結果。

  1. #include 
  2. FILE *popen(constchar *command, constchar *type);  
  3. int pclose(FILE *stream);  
type的引數只能是“r”或"w"

例如

[cpp] view plaincopyprint? #include #include int main(int argc,char*argv[]){       FILE *fstream=NULL;         char buff[1024];       memset(buff,0,sizeof(buff));       if(NULL==(fstream=popen("ls -l","r")))         {            fprintf(stderr,"execute command failed: %s",strerror(errno));             return -1;         }        if(NULL!=fgets(buff, sizeof(buff), fstream))        {            printf("%s",buff);       }        else     {           pclose(fstream);           return -1;       }       pclose(fstream);       return 0;   
  1. }  


linux exec的用法


說是exec系統呼叫,實際上在Linux中,並不存在一個exec()的函式形式,exec指的是一組函式,一共有6個,分別是:
#include 


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 
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");
    }
}

相關推薦

C程式呼叫shell指令碼共有方式system()popen()exec系列函式

1)system(shell命令或shell指令碼路徑);    執行過程:system()會呼叫fork()產生子程序,由子程序來呼叫/bin/sh-c string來執行引數string字串所代表的命令,此命令執行完後隨即返回原呼叫的程序。在呼叫system()期間SI

Shell 指令碼呼叫另一個 Shell 指令碼方式

先來說一下主要以下有幾種方式: fork: 如果指令碼有執行許可權的話,path/to/foo.sh。如果沒有,sh path/to/foo.sh。 exec: exec path/to/foo.sh source: source path/to/foo.s

Shell 指令碼呼叫另一個 Shell 指令碼方式以及返回值問題

指令碼呼叫: 先來說一下主要以下有幾種方式: fork: 如果指令碼有執行許可權的話,path/to/foo.sh。如果沒有,sh path/to/foo.sh。新開啟子shell,需要在父shell定義環境變數的變數子shell才可以使用可以繼承環境變數。在指令碼中

linux的C程式 呼叫 shell指令碼,獲取shell的執行結果

linux下通過C執行命令的時候一半都是使用system()方法,但是該方法執行命令返回的值是-1或0,而有時候我們需要得到執行命令後的結果。可以使用管道實現 輸出到檔案流的函式是popen(),例如 FILE *isr; isr = popen("ls -l","r"

細談 C++ 返回傳值的方式按值返回按常量引用返回以及按引用返回

一、引言 停滯了很久,最近又開始細細品味起《Data Structure And Algorithm Analysis In C++》這本書了。這本書的第一章即為非常好的 C++11 統領介紹的教材範文,可能對於 C++11 新手來說,作者這樣短篇幅的介紹或許有些蒼白晦澀,但是對於我

C# 將程式新增開機啟動的方式

前言 最近在研究程式隨系統啟動,發現在 win7 上因為許可權的問題,寫登錄檔的時候總是會出現問題,寫不進去導致的不能自動啟動,隨後決定仔細的看一看這方面的問題。 查資料過程中主要發現有三種方式可以新增到啟動,分別是: 1. 開始選單啟動; 2. 登錄檔

彙編——子程式呼叫引數傳遞的方式(示例程式個數累加求和)

一、子程式定義 子程式名      PROC     NEAR|FAR            &nbs

python呼叫shell指令碼的兩方法

os.system()和os.popen() 1.python呼叫Shell指令碼,有兩種方法:os.system()和os.popen(), 前者返回值是指令碼的退出狀態碼,後者的返回值是指令碼執行過程中的輸出內容。 >>>help(

android C呼叫SHELL指令碼

  最近在除錯4.2的程式碼發現需要在lunx中所以需要自己想辦法修改, 於是想到了 在C中呼叫指令碼 指令碼: check_voice_value.sh #!/system/bin/sh READ

C++ STL 建立執行緒的方式

使用 stl thread 編寫多執行緒程式時,編譯需要加 -pthread 通過函式指標建立執行緒 #include <iostream> #include <thread> using namespace std; void func(int id

c++】遍歷字串的方式

就以:把字串“1234”轉換為整形1234,為例來說明遍歷字串的三種方式: ①常規方式(下標+operator[]) #include <iostream> #include <string> #include <vector> #include <

執行shell指令碼的四方式(轉)

原文網址:https://www.jb51.net/article/53924.htm 這篇文章主要介紹了Linux中執行shell指令碼的4種方法,即總結在Linux中執行shell指令碼的4種方法。 前提:bash shell 指令碼的方法有多種,現在作個小結。假設我們編寫好的shell指令碼的檔名為

一個JSP頁面呼叫另一個JSP方式

(1)include指令         include指令告訴容器:複製被包含檔案彙總的所有內容,再把它貼上到這個檔案中。 <%@ include file="Header.jsp"%> (2)include標準動作 <jsp:include page

C# 連線 Oracle 資料庫(方式OracleClientODBCOLEDB)

1、OracleClient //基於.NET 2.0,只有2.0中包含OracleClient using System; using System.Collections; using System.ComponentModel; using System.Data;

黃聰C#獲取網頁HTML內容的方式

HttpWebRequest httpReq; HttpWebResponse httpResp; string strBuff = ""; char[] cbuffer = new char[256]; int byteRead = 0; string filename

jquery 呼叫 click 事件 的 方式

第一種方式: $(document).ready(function(){ $("#clickme").click(function(){ alert("Hello World click"); }); 第二種方式: $('#clickmebi

java斐波那契數列(Fibonacci sequence)的方式遞迴,備忘錄,動態規劃

java斐波那契數列(Fibonacci sequence)的三種方式:遞迴,備忘錄,動態規劃 1.最常使用的是遞迴,就是從上往下尋找答案,然後在返回來。 2.備忘錄也是從上往下,只是去掉了遞迴中重複計算的部分,因為它使用一個容器來裝已經計算出的值,這裡就多一個判斷,如果計算過該式子,就直接

【Android】一Progress進度條實現的方式主執行緒實現,Service載入,動態建立

前言 更新版本,上傳資料到服務端,都是需要進度顯示的,Android進度顯示兩種方式 ProgressDialog 和 ProgressBar 新版本中ProgressDialog不被推薦使用,所以專案採用ProgressBar 分為三種實現方式: 1、MainAct

Android自定義View的方式繼承佈局,繼承原生控制元件,繼承View

 自定義View非常的常用,也是Android開發的一項基本技能,自定義View有三種方式:繼承佈局,繼承原生控制元件,繼承View。一、繼承佈局先看效果圖:程式碼實現:1.在layout資料夾中建立佈局title_view.xml,這一步根據自己需要寫,本例中的佈局如下:佈

運維團隊踐行的方式

本文整理自 GOPS • 2017 北京站演講《運維團隊的一種選擇 簡、智、深》,高效運維社群致力於陪伴您的職業生涯,與您一起愉快的成長。 作者:趙建春 編輯:畢巨集飛 新時代下的網際網路運維正在經歷了一場場風雨洗禮,當前時代下有多少運維人處在迷茫和無助之中,交流和學習所積累下來的是更清晰,還是更