1. 程式人生 > >linux下system函式詳解

linux下system函式詳解

一、system函式的簡單介紹

標頭檔案 
 #include <stdlib.h>

函式定義
 int system(const char * string); 

函式說明

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

二、system函式的返回值

status = system("./test.sh")

1、先統一兩個說法:
(1)system返回值:指呼叫system函式後的返回值,比如上例中status為system返回值
(2)shell返回值:指system所呼叫的shell命令的返回值,比如上例中,test.sh中返回的值為shell返回值。

2、man中對於system的說明

   RETURN VALUE       
   The value returned is -1 on error (e.g.  fork() 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).

system函式對返回值的處理,涉及3個階段:
階段1:建立子程序等準備工作。如果失敗,返回-1。
階段2:呼叫/bin/sh拉起shell指令碼,如果拉起失敗或者shell未正常執行結束(參見備註1),原因值被寫入到status的低8~15位元位中。system的man中只說明瞭會寫了127這個值,但實測發現還會寫126等值。
階段3:如果shell指令碼正常執行結束,將shell返回值填到status的低8~15位元位中。

只要能夠呼叫到/bin/sh,並且執行shell過程中沒有被其他訊號異常中斷,都算正常結束。比如:不管shell指令碼中返回什麼原因值,是0還是非0,都算正常執行結束。即使shell指令碼不存在或沒有執行許可權,也都算正常執行結束。如果shell指令碼執行過程中被強制kill掉等情況則算異常結束。如何判斷階段2中,shell指令碼是否正常執行結束呢?系統提供了巨集:WIFEXITED(status)。如果WIFEXITED(status)為真,則說明正常結束。如何取得階段3中的shell返回值?你可以直接通過右移8bit來實現,但安全的做法是使用系統提供的巨集:WEXITSTATUS(status)。

由於我們一般在shell指令碼中會通過返回值判斷本指令碼是否正常執行,如果成功返回0,失敗返回正數。所以綜上,判斷一個system函式呼叫shell指令碼是否正常結束的方法應該是如下3個條件同時成立:
(1)-1 != status
(2)WIFEXITED(status)為真
(3)0 == WEXITSTATUS(status)

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
 int main()
 {   
 	 pid_t status;    
   	 status = system("./test.sh"); 
     if (-1 == status) 
     {     
     	 printf("system error!");  
     }   
     else 
     {    
           printf("exit status value = [0x%x]\n", status);    
            if (WIFEXITED(status))     
             {        
                    if (0 == WEXITSTATUS(status))    
                     {              
                           printf("run shell script successfully.\n");        
                     }          
                      else        
                       {             
                           printf("run shell script fail, script exit code: %d\n", WEXITSTATUS(status));      
                       }      
               }      
               else  
                {    
                        printf("exit status = [%d]\n", WEXITSTATUS(status));   
                  }   
         }    
        return 0;
  }

為了更好的理解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()函式的實現:

int system(const char * cmdstring) 
{ 
	pid_t pid;  
    int status; 
    if(cmdstring == NULL) 
    { 
       return (1); //如果cmdstring為空,返回非零值,一般為1 
    } 
    if((pid = fork())<0) 
    { 
       status = -1; //fork失敗,返回-1 
    } else if(pid == 0)     
    { 
		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); 
        _exit(127); // exec執行失敗返回127,注意exec只在失敗時才返回現在的程序,成功的話現在的程序就不存在啦~~
    } 
    else //父程序 
    {
        while(waitpid(pid, &status, 0) < 0) 
	   { 
			if(errno != EINTR) 
		    { 
				 status = -1; //如果waitpid被訊號中斷,則返回-1 
				 break; 
		    } 
	   } 
     } 
   return status; //如果waitpid成功,則返回子程序的返回狀態 
 } 

三、呼叫system函式的問題

先看一下問題 簡單封裝了一下system()函式:

int pox_system(const char *cmd_line) 
{ 
	return system(cmd_line); 
} 

函式呼叫:

int ret = 0; 
ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z"); 
if(0 != ret) 
{ 
 	Log("zip file failed\n"); 
}

問題現象:每次執行到此處,都會zip failed。而單獨把該命令拿出來在shell裡執行卻總是對的,事實上該段程式碼已運行了很長時間,從沒出過問題。
分析log時,我們只能看到“zip file failed”這個我們自定義的資訊,至於為什麼fail,毫無線索。 那好,我們先試著找出更多的線索:

 int ret = 0; 
 ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z"); 
 if(0 != ret) 
 { 
 	Log("zip file failed: %s\n", strerror(errno)); //嘗試打印出系統錯誤資訊 
 } 

我們增加了log,通過system()函式設定的errno,我們得到一個非常有用的線索:system()函式失敗是由於“ No child processes”。
我們通過上面的線索,知道system()函式設定了errno為ECHILD,然而從system()函式的man手冊裡我們找不到任何有關EHILD的資訊。我們知道system()函式執行過程為:fork()->exec()->waitpid(). 很顯然waitpid()有重大嫌疑,我們去查一下man手冊,看該函式有沒有可能設定

ECHILD: ECHILD (for waitpid() or waitid()) The process specified by pid (waitpid()) or idtype and id (waitid()) 
does not exist or is not a child of the calling process. (This can happen for one's own child if the action for 
SIGCHLD is set to SIG_IGN. See also the Linux Notes section about threads.) 

果然有料,如果SIGCHLD訊號行為被設定為SIG_IGN時,waitpid()函式有可能因為找不到子程序而報ECHILD錯誤。似乎我們找到了問題的解決方案:在呼叫system()函式前重新設定SIGCHLD訊號為預設值,即signal(SIGCHLD, SIG_DFL)。

system()函式之前沒出錯,是因為systeme()函式依賴了系統的一個特性,那就是核心初始化程序時對SIGCHLD訊號的處理方式為SIG_DFL,這是什麼什麼意思呢?即核心發現程序的子程序終止後給程序傳送一個SIGCHLD訊號,程序收到該訊號後採用SIG_DFL方式處理,那麼SIG_DFL又是什麼方式呢?SIG_DFL是一個巨集,定義了一個訊號處理函式指標,事實上該訊號處理函式什麼也沒做。這個特性正是system()函式需要的,system()函式首先fork()一個子程序執行command命令,執行完後system()函式會使用waitpid()函式對子程序進行收屍。 通過上面的分析,我們可以清醒的得知,system()執行前,SIGCHLD訊號的處理方式肯定變了,不再是SIG_DFL了,至於變成什麼暫時不知道,事實上,我們也不需要知道,我們只需要記得使用system()函式前把SIGCHLD訊號處理方式顯式修改為SIG_DFL方式,同時記錄原來的處理方式,使用完system()後再設為原來的處理方式。

問題分析到這裡,解決方法也清晰了,於是我們修改了我們的pox_system()函式:

typedef void (*sighandler_t)(int); 
int pox_system(const char *cmd_line) 
{ 
 int ret = 0;     
 sighandler_t old_handler;     
 old_handler = signal(SIGCHLD, SIG_DFL);     
 ret = system(cmd_line);     
 signal(SIGCHLD, old_handler);     
 return ret;     
} 

四、瞭解popen函式

標準I/O函式庫提供了popen函式,它啟動另外一個程序去執行一個shell命令列。
這裡我們稱呼叫popen的程序為父程序,由popen啟動的程序稱為子程序。
popen函式還建立一個管道用於父子程序間通訊。父程序要麼從管道讀資訊,要麼向管道寫資訊,至於是讀還是寫取決於父程序呼叫popen時傳遞的引數。下在給出popen、pclose的定義。

#include <stdio.h>
/*
函式功能:popen()會呼叫fork()產生子程序,然後從子程序中呼叫/bin/sh -c來執行引數command的指令。
        引數type可使用“r”代表讀取,“w”代表寫入。
        依照此type值,popen()會建立管道連到子程序的標準輸出裝置或標準輸入裝置,然後返回一個檔案指標。
        隨後程序便可利用此檔案指標來讀取子程序的輸出裝置或是寫入到子程序的標準輸入裝置中
返回值:若成功則返回檔案指標,否則返回NULL,錯誤原因存於errno中
*/
FILE * popen( const char * command,const char * type);

/*
函式功能:pclose()用來關閉由popen所建立的管道及檔案指標。引數stream為先前由popen()所返回的檔案指標
返回值:若成功返回shell的終止狀態(也即子程序的終止狀態),若出錯返回-1,錯誤原因存於errno中
*/
int pclose(FILE * stream);

下面通過例子看下popen的使用:

假如我們想取得當前目錄下的檔案個數,在shell下我們可以使用:

ls | wc -l

我們可以在程式中這樣寫:

/*取得當前目錄下的檔案個數*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>

#define MAXLINE 1024

int main()
{
	char result_buf[MAXLINE], command[MAXLINE];
	int rc = 0; // 用於接收命令返回值
	FILE *fp;

	/*將要執行的命令寫入buf*/
	snprintf(command, sizeof(command), "ls ./ | wc -l");

	/*執行預先設定的命令,並讀出該命令的標準輸出*/
	fp = popen(command, "r");
	if(NULL == fp)
	{
		perror("popen執行失敗!");
		exit(1);
	}
	while(fgets(result_buf, sizeof(result_buf), fp) != NULL)
	{
		/*為了下面輸出好看些,把命令返回的換行符去掉*/
		if('\n' == result_buf[strlen(result_buf)-1])
		{
			result_buf[strlen(result_buf)-1] = '\0';
		}
		printf("命令【%s】 輸出【%s】\r\n", command, result_buf);
	}

	/*等待命令執行完畢並關閉管道及檔案指標*/
	rc = pclose(fp);
	if(-1 == rc)
	{
		perror("關閉檔案指標失敗");
		exit(1);
	}
	else
	{
		printf("命令【%s】子程序結束狀態【%d】命令返回值【%d】\r\n", command, rc, WEXITSTATUS(rc));
	}

	return 0;
}

編譯並執行:
$ gcc popen.c

$ ./a.out

命令【ls ./ | wc -l】 輸出【2】

命令【ls ./ | wc -l】子程序結束狀態【0】命令返回值【0】

上面popen只捕獲了command的標準輸出,如果command執行失敗,子程序會把錯誤資訊列印到標準錯誤輸出,父程序就無法獲取。比如,command命令為“ls nofile.txt” ,事實上我們根本沒有nofile.txt這個檔案,這時shell會輸出“ls: nofile.txt: No such file or directory”。這個輸出是在標準錯誤輸出上的。通過上面的程式並無法獲取。

注:如果你把上面程式中的command設成“ls nofile.txt”,編譯執行程式你會看到如下結果:

$ gcc popen.c

$ ./a.out

ls: nofile.txt: No such file or directory

命令【ls nofile.txt】子程序結束狀態【256】命令返回值【1】

需要注意的是第一行輸出並不是父程序的輸出,而是子程序的標準錯誤輸出。

有時子程序的錯誤資訊是很有用的,那麼父程序怎麼才能獲取子程序的錯誤資訊呢?

這裡我們可以重定向子程序的錯誤輸出,讓錯誤輸出重定向到標準輸出(2>&1),這樣父程序就可以捕獲子程序的錯誤資訊了。例如command為“ls nofile.txt 2>&1”,輸出如下:

命令【ls nofile.txt 2>&1】 輸出【ls: nofile.txt: No such file or directory】

命令【ls nofile.txt 2>&1】子程序結束狀態【256】命令返回值【1】

五、用來popen函式代替system函式

int my_system(const char * cmd) 
{ 
    FILE * fp;     
    int res; char buf[1024]; 
    if (cmd == NULL) 
    { 
       printf("my_system cmd is NULL!\n");
       return -1;
    } 
    if ((fp = popen(cmd, "r") ) == NULL) 
    { 
       perror("popen");
       printf("popen error: %s/n", strerror(errno));
       return -1; 
    } 
   else
   {
       while(fgets(buf, sizeof(buf), fp)) 
      { 
          printf("%s", buf); 
      } 
      if ( (res = pclose(fp)) == -1) 
      { 
         printf("close popen file pointer fp error!\n"); return res;
      } 
      else if (res == 0) 
      {
          return res;
      } 
     else 
     { 
         printf("popen res is :%d\n", res);
         return res; 
     } 
   }
} 

轉載出處:https://blog.csdn.net/dilireba/article/details/78645755