1. 程式人生 > 其它 >Linux下system () 函式詳解簡介

Linux下system () 函式詳解簡介

技術標籤:筆記linux

Linux下system (),popen 函式詳解簡介

轉載

執行shell 命令)

相關函式

fork,execve,waitpid,popen

表頭檔案

#include<stdlib.h>

定義函式

int system(const char * string);

函式說明

system()會呼叫fork()產生子程序,由子程序來呼叫/bin/sh-c string來執行引數string字串所代表的命令,此命令執行完後隨即返回原呼叫的程序。在呼叫system()期間SIGCHLD 訊號會被暫時擱置,SIGINT和SIGQUIT 訊號則會被忽略。

返回值

如果fork()失敗 返回-1:出現錯誤

如果exec()失敗,表示不能執行Shell,返回值相當於Shell執行了exit(127)

如果執行成功則返回子Shell的終止狀態

如果system()在呼叫/bin/sh時失敗則返回127,其他失敗原因返回-1。若引數string為空指標(NULL),則返回非零值>。如果system()呼叫成功則最後會返回執行shell命令後的返回值,但是此返回值也有可能為 system()呼叫/bin/sh失敗所返回的127,因此最好能再檢查errno 來確認執行成功。

附加說明

在編寫具有SUID/SGID許可權的程式時請勿使用system(),system()會繼承環境變數,通過環境變數可能會造成系統安全的問題。

System與exec的區別

1、system()和exec()都可以執行程序外的命令,system是在原程序上開闢了一個新的程序,但是exec是用新程序(命令)覆蓋了原有的程序

2、system()和exec()都有能產生返回值,system的返回值並不影響原有程序,但是exec的返回值影響了原程序

system()函式功能強大,很多人用卻對它的原理知之甚少先看linux版system函式的原始碼:

複製程式碼

#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>

int system(const char * cmdstring)
{
    pid_t pid;
    int status;

    if(cmdstring == NULL){
           
         return (1);
    }

    if((pid = fork())<0){

            status = -1;
    }else if(pid = 0){

        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);

        -exit(127); //子程序正常執行則不會執行此語句

    }else{
            while(waitpid(pid, &status, 0) < 0){

                if(errno != EINTER){

                    status = -1;

                    break;

                }
            }
        }
        return status;
}

複製程式碼

先分析一下原理,然後再看上面的程式碼大家估計就能看懂了:
當system接受的命令為NULL時直接返回,否則fork出一個子程序,因為fork在兩個程序:父程序和子程序中都返回,這裡要檢查返回的pid,fork在子程序中返回0,在父程序中返回子程序的pid,父程序使用waitpid等待子程序結束,子程序則是呼叫execl來啟動一個程式代替自己,execl("/bin/sh", “sh”, “-c”, cmdstring, (char*)0)是呼叫shell,這個shell的路徑是/bin/sh,後面的字串都是引數,然後子程序就變成了一個shell程序,這個shell的引數
是cmdstring,就是system接受的引數。在windows中的shell是command,想必大家很熟悉shell接受命令之後做的事了。

如果上面的你沒有看懂,那我再解釋下fork的原理:當一個程序A呼叫fork時,系統核心建立一個新的程序B,並將A的記憶體映像複製到B的程序空間中,因為A和B是一樣的,那麼他們怎麼知道自己是父程序還是子程序呢,看fork的返回值就知道,上面也說了fork在子程序中返回0,在父程序中返回子程序的pid。

windows中的情況也類似,就是execl換了個又臭又長的名字,引數名也換的看了讓人發暈的,我在MSDN中找到了原型,給大家看看:

複製程式碼

HINSTANCE   ShellExecute(
               HWND   hwnd,
               LPCTSTR   lpVerb,
               LPCTSTR   lpFile,
               LPCTSTR   lpParameters,
               LPCTSTR   lpDirectory, 
               INT   nShowCmd 
   ); 

複製程式碼

用法見下:

ShellExecute(NULL,   "open",   "c:\\a.reg",   NULL,   NULL,   SW_SHOWNORMAL);   

你也許會奇怪 ShellExecute中有個用來傳遞父程序環境變數的引數 lpDirectory,linux中的 execl卻沒有,這是因為execl是編譯器的函式(在一定程度上隱藏具體系統實現),在linux中它會接著產生一個linux系統的呼叫 execve, 原型見下:

int execve(const char * file,const char **argv,const char **envp);

看到這裡你就會明白為什麼system()會接受父程序的環境變數,但是用system改變環境變數後,system一返回主函式還是沒變。原因從system的實現可以看到,它是通過產生新程序實現的,從我的分析中可以看到父程序和子程序間沒有程序通訊,子程序自然改變不了父程序的環境變數。希望小菜們不要拿tc或使用tc庫的其他編譯器中的system的呼叫結果來反駁我,這不是一個概念,DOS早死翹翹了,玩linux吧。就說到這裡了。

c語言呼叫Linux的命令system(" "); popen

exec 系列

execl等等類似的函式都可以執行任何shell下的命令。

#include <stdlib.h>
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[]);

也可以,不同之處是前者可以執行完畢而不退出,後者執行完畢,正確會推出,錯誤則不退出,繼續下面的語句。

【C/C++】Linux下使用system()函式一定要謹慎

http://my.oschina.net/renhc/blog/53580

曾經的曾經,被system()函式折磨過,之所以這樣,是因為對system()函數了解不夠深入。只是簡單的知道用這個函式執行一個系統命令,這遠遠不夠,它的返回值、它所執行命令的返回值以及命令執行失敗原因如何定位,這才是重點。當初因為這個函式風險較多,故拋棄不用,改用其他的方法。這裡先不說我用了什麼方法,這裡必須要搞懂system()函式,因為還是有很多人用了system()函式,有時你不得不面對它。

先來看一下system()函式的簡單介紹:

view source

print?

1#include <stdlib.h>
2int system``(``const char *command);

system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored.

system()函式呼叫/bin/sh來執行引數指定的命令,/bin/sh 一般是一個軟連線,指向某個具體的shell,比如bash,-c選項是告訴shell從字串command中讀取命令;

在該command執行期間,SIGCHLD是被阻塞的,好比在說:hi,核心,這會不要給我送SIGCHLD訊號,等我忙完再說;

在該command執行期間,SIGINT和SIGQUIT是被忽略的,意思是程序收到這兩個訊號後沒有任何動作。

再來看一下system()函式返回值:

The value returned is -1 on error (e.g. fork(2) failed), and the return status of the command otherwise. This latter return status is in the format specified in wait(2). Thus, the exit code of the command will be WEXITSTATUS(status). In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127).

If the value of command is NULL, system() returns nonzero if the shell is available, and zero if not.

為了更好的理解system()函式返回值,需要了解其執行過程,實際上system()函式執行了三步操作:

1.fork一個子程序;

2.在子程序中呼叫exec函式去執行command;

3.在父程序中呼叫wait去等待子程序結束。

對於fork失敗,system()函式返回-1。

如果exec執行成功,也即command順利執行完畢,則返回command通過exit或return返回的值。

(注意,command順利執行不代表執行成功,比如command:“rm debuglog.txt”,不管檔案存不存在該command都順利執行了)

如果exec執行失敗,也即command沒有順利執行,比如被訊號中斷,或者command命令根本不存在,system()函式返回127.

如果command為NULL,則system()函式返回非0值,一般為1.

看一下system()函式的原始碼

看完這些,我想肯定有人對system()函式返回值還是不清楚,看原始碼最清楚,下面給出一個system()函式的實現:

view source

print?

01int system``(``const char * cmdstring)
02{
03``pid_t pid;
04``int status;
05
06if``(cmdstring == NULL)
07{
08``return (1); ``//如果cmdstring為空,返回非零值,一般為1
09}
10
11if``((pid = fork())<0)
12{
13``status = -1; ``//fork失敗,返回-1
14}
15else if``(pid == 0)
16{
17``execl(``"/bin/sh"``, ``"sh"``, ``"-c"``, cmdstring, (``char *)0);
18``_exit(127); ``// exec執行失敗返回127,注意exec只在失敗時才返回現在的程序,成功的話現在的程序就不存在啦~~
19}
20else //父程序
21{
22``while``(waitpid(pid, &status, 0) < 0)
23``{
24``if``(``errno != EINTR)
25``{
26``status = -1; ``//如果waitpid被訊號中斷,則返回-1
27``break``;
28``}
29``}
30}
31
32``return status; ``//如果waitpid成功,則返回子程序的返回狀態
33}

仔細看完這個system()函式的簡單實現,那麼該函式的返回值就清晰了吧,那麼什麼時候system()函式返回0呢?只在command命令返回0時。

看一下該怎麼監控system()函式執行狀態

這裡給我出的做法:

view source

print?

01int status;
02if``(NULL == cmdstring) ``//如果cmdstring為空趁早閃退吧,儘管system()函式也能處理空指標
03{
04``return XXX;
05}
06status = ``system``(cmdstring);
07if``(status < 0)
08{
09``printf``(``"cmd: %s\t error: %s"``, cmdstring, ``strerror``(``errno``)); ``// 這裡務必要把errno資訊輸出或記入Log
10``return XXX;
11}
12
13if``(WIFEXITED(status))
14{
15``printf``(``"normal termination, exit status = %d\n"``, WEXITSTATUS(status)); ``//取得cmdstring執行結果
16}
17else if``(WIFSIGNALED(status))
18{
19``printf``(``"abnormal termination,signal number =%d\n"``, WTERMSIG(status)); ``//如果cmdstring被訊號中斷,取得訊號值
20}
21else if``(WIFSTOPPED(status))
22{
23``printf``(``"process stopped, signal number =%d\n"``, WSTOPSIG(status)); ``//如果cmdstring被訊號暫停執行,取得訊號值
24}

到於取得子程序返回值的相關介紹可以參考另一篇文章:http://my.oschina.net/renhc/blog/35116

system()函式用起來很容易出錯,返回值太多,而且返回值很容易跟command的返回值混淆。這裡推薦使用popen()函式替代,關於popen()函式的簡單使用也可以通過上面的連結檢視。

popen()函式較於system()函式的優勢在於使用簡單,popen()函式只返回兩個值:
成功返回子程序的status,使用WIFEXITED相關巨集就可以取得command的返回結果;
失敗返回-1,我們可以使用perro()函式或strerror()函式得到有用的錯誤資訊。

這篇文章只涉及了system()函式的簡單使用,還沒有談及SIGCHLD、SIGINT和SIGQUIT對system()函式的影響,事實上,之所以今天寫這篇文章,是因為專案中因有人使用了system()函式而造成了很嚴重的事故。現像是system()函式執行時會產生一個錯誤:“No child processes”。

popen函式

popen(執行shell 命令,同時可以獲取執行的輸出結果)

popen比system的優點是,popen可以獲取執行的輸出結果

需要包含的標頭檔案
#include<stdio.h>
1
函式原型:

FILE popen( const char command, const char* mode )

引數說明:

command: 是一個指向以 NULL 結束的 shell 命令字串的指標。這行命令將被傳到 bin/sh 並使用 -c 標誌,shell 將執行這個命令。
mode: 只能是讀或者寫中的一種,得到的返回值(標準 I/O 流)也具有和 type 相應的只讀或只寫型別。
如果 type 是 “r” 則檔案指標連線到 command 的標準輸出;
如果 type 是 “w” 則檔案指標連線到 command 的標準輸入。
int pclose (FILE* stream)

引數說明:

stream:popen返回的檔案指標

返回值:

成功,則返回一個讀或者開啟檔案的指標
失敗,返回NULL,具體錯誤要根據errno判斷

注意:

讀取:需要使用fread()來讀取popen產生的管道中的內容

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
1

關閉:popen() 函式用於建立一個管道:其內部實現為呼叫 fork 產生一個子程序,執行一個 shell 以執行命令來開啟一個程序這個程序必須由 pclose() 函式關閉。

int fclose(FILE *fp);
1
例項:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
        char ret[1024]={0};
        FILE *fp=NULL;
        int nread=0;
        fp=popen("ps","r");
        nread=fread(ret,1,1024,fp);
        printf("read ret %d byte,ret=%s\n",nread,ret);
        pclose(fp);
        return 0;
}
read(void *ptr, size_t size, size_t nmemb, FILE *stream);
1

關閉:popen() 函式用於建立一個管道:其內部實現為呼叫 fork 產生一個子程序,執行一個 shell 以執行命令來開啟一個程序這個程序必須由 pclose() 函式關閉。

int fclose(FILE *fp);
1
例項:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
        char ret[1024]={0};
        FILE *fp=NULL;
        int nread=0;
        fp=popen("ps","r");
        nread=fread(ret,1,1024,fp);
        printf("read ret %d byte,ret=%s\n",nread,ret);
        pclose(fp);
        return 0;
}