System函數返回值 和popen


 

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

 

 

 

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

 

#include <stdlib.h> int 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()函數的實現:

 

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()函數返回0呢?只在command命令返回0時。

 

 

 

看一下該怎么監控system()函數執行狀態

 

這里給我出的做法:

 

int status; if(NULL == cmdstring) //如果cmdstring為空趁早閃退吧,盡管system()函數也能處理空指針 { return XXX; } status = system(cmdstring); if(status < 0) { printf("cmd: %s\t error: %s", cmdstring, strerror(errno)); // 這里務必要把errno信息輸出或記入Log return XXX; } if(WIFEXITED(status)) { printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); //取得cmdstring執行結果 } else if(WIFSIGNALED(status)) { printf("abnormal termination,signal number =%d\n", WTERMSIG(status)); //如果cmdstring被信號中斷,取得信號值 } else if(WIFSTOPPED(status)) { printf("process stopped, signal number =%d\n", WSTOPSIG(status)); //如果cmdstring被信號暫停執行,取得信號值 }

 

 

到於取得子進程返回值的相關介紹可以參考另一篇文章: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”。

 

關於這個錯誤的分析,感興趣的朋友可以看一下:http://my.oschina.net/renhc/blog/54582

 

 

=======================================================================================

 

  APUE這本書,對system這個函數已經將的比較明白了,只是它的相關知識稍顯分散。最開始我是去網上找的資料,自己寫的測試代碼,可是還是有很多迷惑的地方。后來才拿起APUE ,好好讀了第八章和第十章的相關章節。    

  1. #include <stdlib.h>
  2. int system(const char *command);
     system的作用是在shell終端執行command。簡單的說就是在C中執行system("ls")這行代碼的含義就相當於在shell執行ls一樣。這么說還是比較籠統,下面詳細描述之:
 
    system是個綜合的操作,分解開來看就是相當於執行了
1 fork  生成一個子進程。
2 在子進程執行 execl("/bin/sh","sh","-c" command,(char*)0);
3 waitpid
 
下面進入正題,返回值:
1 如果fork失敗了,或者waitpid返回除了EINTR之外的錯誤,system返回 -1;
2 execl執行失敗,其返回值如同shell執行了"exit(127)" 一樣。
3 如果上述三步都執行成功,那么,system返回值是shell的終止狀態。
 
 
上面這些話是APUE的,很抽象,很不具體,很籠統,我現在結合手冊和代碼解釋一下。
 
手冊中有這么一段話:
  1. 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
  2.  exit(127).
1 如果/bin/sh拉起shell命令失敗,或者是shell命令沒有正常執行 (比如命令根本就是非法的命令),那么,將原因填入status的8~15位
  手冊中也說如果命令執行不起來,那么相當於執行exit
  1. libin@libin:~/program/C/Linux/system$ ./tsys "nosuchcmd"
  2. sh: nosuchcmd: not found
  3. status = 32512
  4. normal termination,exit status = 127
們看到了,nosuchcmd不是shell支持的命令,所以,shell命令返回了127,對於system函數,返回值為 127*256 = 32512;因為shell的返回值是 system返回值的8~15位
 
2  如果shell順利執行完畢,那么將shell的返回值填到system返回值的8~15位
 這里需要強調以下,所謂順利執行完畢,並不是說,命令command執行成功,而是指  /bin/sh順利調用,執行期間沒有被信號異常終止,這都算是順利執行完畢。
  
看下面的例子:
 
  1. libin@libin:~/program/C/Linux/system$ ./tsys "ls /noexisted"
  2. ls: 無法訪問/noexisted: 沒有那個文件或目錄
  3. status = 512
  4. normal termination,exit status = 2
  5.  
  6. libin@libin:~/program/C/Linux/system$ ls /noexist
  7. ls: 無法訪問/noexist: 沒有那個文件或目錄
  8. libin@libin:~/program/C/Linux/system$ echo $?
  9. 2
  10. libin@libin:~/program/C/Linux/system$ 
    我們看到了,雖然/noexist文件並不存在,ls這條命令執行出了錯,但是仍然屬於shell順利執行完畢。 ls /noexist的錯誤嗎是2,所以,system函數的返回值為 2*256 = 512.
 
    各位可能比較感興趣的是,如果我知道system的返回值,如何知道我的命令的返回值呢?手冊中有這么一句話:
  1. Thus, the exit code of the command will be WEXITSTATUS(status)
    看到了WEXITSTATUS(status),就是command的返回值。當然前提條件是shell命令順利執行完畢。即
  1. WIFEXITED(status) ! =0
 
3 前面的討論都沒有考慮信號,如果shell在執行過程中收到了信號。
這個問題我存在有疑惑,因為APUE第十章講到,system的實現要忽略SIGINT和SIGQUIT,但是我的實驗結果並不是這樣,如有高手知道,請不吝賜教。
 
首先看我的終端信號配置:
  1. libin@libin:~/program/C/Linux/system$ stty -a
  2. speed 38400 baud; rows 36; columns 134; line = 0;
  3. intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S;susp = ^Z;
  4. rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
  5. -parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
  6. -ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8
  7. opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
  8. isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
    Ctrl+C產生SIGINT,Ctrl+\ 產生 SIGQUIT。
    看下測試結果
  1. libin@libin:~/program/C/Linux/system$ ./tsys "sleep 7"
  2. ^Cstatus = 2
  3. abnormal termination,signal number =2
  4. libin@libin:~/program/C/Linux/system$ sleep 7
  5. ^C
  6. libin@libin:~/program/C/Linux/system$ echo $?
  7. 130
    我們可以看到,直接在終端執行sleep 7,然后用Ctrl+C發送SIGINT信號,異常退出,shell返回值為130,130的原因,APUE上解釋了,因為SIGINT 等於2,終止狀態是128+信號編號,所以為130.
 
    按照APUE上,我們的system調用應該將SIGINT忽略掉,然后正常返回,同時返回值為130,但是實際上LINUX下並不是這樣的。實際上system的返回值為2,並且異常退出了。
 
    SIGQUIT信號也是一樣的,看我在我的Ubuntu 上做的測試:
  1. libin@libin:~/program/C/Linux/system$ ./tsys "sleep 7"
  2. ^\status = 3
  3. abnormal termination,signal number =3
  4. libin@libin:~/program/C/Linux/system$ sleep 7
  5. ^\退出
  6. libin@libin:~/program/C/Linux/system$ echo $?
  7. 131
 
    2012年1月1日,我做了下面的實驗測試,下面是實驗的過程。
  1. root@libin:~/program/C/Linux/system# ./tsys "sleep 50" &
  2. [1] 2518
  3. root@libin:~/program/C/Linux/system# ps -ef
  4. root 2359 2343 0 12:42 pts/0 00:00:00 /bin/bash
  5. root 2518 2359 0 12:55 pts/0 00:00:00 ./tsys sleep 50
  6. root 2519 2518 0 12:55 pts/0 00:00:00 sh -c sleep 50
  7. root 2520 2519 0 12:55 pts/0 00:00:00 sleep 50
  8. root 2521 2359 0 12:56 pts/0 00:00:00 ps -ef
  9. root@libin:~/program/C/Linux/system# kill -3 2520
  10. Quit
  11. status = 33536
  12. normal termination,exit status = 131
  13. root@libin:~/program/C/Linux/system# ./tsys "sleep 50" &
  14. [1] 2568
  15. root@libin:~/program/C/Linux/system# ps -ef
  16. root 2568 2359 0 13:01 pts/0 00:00:00 ./tsys sleep 50
  17. root 2569 2568 0 13:01 pts/0 00:00:00 sh -c sleep 50
  18. root 2570 2569 0 13:01 pts/0 00:00:00 sleep 50
  19. root 2571 2359 0 13:01 pts/0 00:00:00 ps -ef
  20. root@libin:~/program/C/Linux/system# kill -3 2569
  21. status = 3
  22. abnormal termination,signal number =3
 
    從測試結果上看,基本上進程關系如下,system的返回值,其實是shell的終止狀態,
而不是sleep的返回值。所謂屏蔽INTR信號和QUIT信號,指的是tsys這個進程,忽略了INT信號的QUIT信號,但是sh進程,和sleep進程,都沒有忽略這兩個信號。
 
    如果給sleep進程發送SIGQUIT信號,殺死sleep進程,那么sleep會返回131,告訴shell進程我被SIGQUIT殺死了。而sh進程,則是安然退出,所以,依然打印出正常退出的打印,通過調用WEXITSTATUS,看以看出,sleep進程的遺言是131,表明收到QUIT信號。
 
 
    下面是測試程序,基本是照抄的APUE。
  1. #define _XOPEN_SOURCE
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<unistd.h>
  5. #include<signal.h>
  6. #include<sys/wait.h>
  7. void pr_exit(int status)
  8. {
  9.         printf("status = %d\n",status);
  10.     if(WIFEXITED(status))
  11.         {
  12.          printf("normal termination,exit status = %d\n",WEXITSTATUS(status));
  13.         }
  14.         else if(WIFSIGNALED(status))
  15.         {
  16.          printf("abnormal termination,signal number =%d%s\n",
  17.                                 WTERMSIG(status),
  18. #ifdef WCOREDUMP
  19.                                 WCOREDUMP(status)?"core file generated" : "");
  20. #else
  21.                 "");
  22. #endif
  23.         }
  24. }
  25. int main(int argc,char* argv[])
  26. {
  27.         int status;
  28.         if(argc<2)
  29.         {
  30.          fprintf(stderr,"usage:tsys cmd\n");
  31.                  return -1;
  32.         }
  33.         if((status = system(argv[1]) )<0)
  34.         {
  35.          fprintf(stderr,"system error\n");
  36.                 return -2;
  37.         }
  38.         pr_exit(status);
  39.     return 0;
  40. }
十分感謝Heartwork的回復,本應該及早致謝,但是由於本周加班太多,機會每天都是10點以后到家,所以沒有時間細細揣摩Heartwork的恢復。
 
參考文獻:
1 APUE 
 
 ================================================================
 
 

一,system()理解

功能:system()函數調用“/bin/sh -c command”執行特定的命令,阻塞當前進程直到command命令執行完畢

原型:

int system(const char *command);

返回值:

如果無法啟動shell運行命令,system將返回127;出現不能執行system調用的其他錯誤時返回-1。如果system能夠順利執行,返回那個命令的退出碼。

說明:

man幫助:

       #include <stdlib.h>

       int system(const char *command);

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

RETURN VALUE 
       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 non-zero if the shell 
       is available, and zero if not.

       system() does not affect the wait status of any other children.

二,system()函數原理

system函數執行時,會調用fork、execve、waitpid等函數。

linux版system函數的源碼:

復制代碼
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()會調用fork()產生子進程,由子進程來調用/bin/sh-c string來執行參數string字符串所代表的命令,此命>令執行完后隨即返回原調用的進程。 
    在調用system()期間SIGCHLD 信號會被暫時擱置,SIGINT和SIGQUIT 信號則會被忽略。 
    返回值 
    =-1:出現錯誤  
    =0:調用成功但是沒有出現子進程  
    >0:成功退出的子進程的id 
    如果system()在調用/bin/sh時失敗則返回127,其他失敗原因返回-1。若參數string為空指針(NULL),則返回非零值>。如果system()調用成功則最后會返回 
    執行shell命令后的返回值,但是此返回值也有可能為 system()調用/bin/sh失敗所返回的127,因此最好能再檢查errno 來確認執行成功。 
    附加說明 
    在編寫具有SUID/SGID權限的程序時請勿使用system(),system()會繼承環境變量,通過環境變量可能會造成系統安全的問題。

system函數對返回值的處理,涉及3個階段:


階段1:創建子進程等准備工作。如果失敗,返回-1。 
階段2:調用/bin/sh拉起shell腳本,如果拉起失敗或者shell未正常執行結束(參見備注1),原因值被寫入到status的低8~15比特位中。system的man中只說明了會寫了127這個值,但實測發現還會寫126等值。 
階段3:如果shell腳本正常執行結束,將shell返回值填到status的低8~15比特位中。 
備注1: 
只要能夠調用到/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) 
注意: 
根據以上分析,當shell腳本不存在、沒有執行權限等場景下時,以上前2個條件仍會成立,此時WEXITSTATUS(status)為127,126等數值。 
所以,我們在shell腳本中不能將127,126等數值定義為返回值,否則無法區分中是shell的返回值,還是調用shell腳本異常的原因值。shell腳本中的返回值最好多1開始遞增。

示例程序:

復制代碼
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define EXIT_ERR(m) \
do\
{\
    perror(m);\
    exit(EXIT_FAILURE);\
}\
while (0);\

int main(void)
{
    int status ;
    status = system("ls -l|wc -l");

    if(status == -1){
        EXIT_ERR("system error");
    }

    else{
        if(WIFEXITED(status))
        {
            if(WEXITSTATUS(status) == 0)
                printf("run command successful\n");
            else
                printf("run command fail and exit code is %d\n",WEXITSTATUS(status));
        }
        else
            printf("exit status = %d\n",WEXITSTATUS(status));
    }
    return 0;
}
復制代碼

結果:

QQ截圖20130713131149

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM