#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.
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時。
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 ,好好讀了第八章和第十章的相關章節。
- #include <stdlib.h>
- int system(const char *command);
- 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).
- libin@libin:~/program/C/Linux/system$ ./tsys "nosuchcmd"
- sh: nosuchcmd: not found
- status = 32512
- normal termination,exit status = 127
- libin@libin:~/program/C/Linux/system$ ./tsys "ls /noexisted"
- ls: 無法訪問/noexisted: 沒有那個文件或目錄
- status = 512
- normal termination,exit status = 2
- libin@libin:~/program/C/Linux/system$ ls /noexist
- ls: 無法訪問/noexist: 沒有那個文件或目錄
- libin@libin:~/program/C/Linux/system$ echo $?
- 2
- libin@libin:~/program/C/Linux/system$
- Thus, the exit code of the command will be WEXITSTATUS(status)
- WIFEXITED(status) ! =0
- libin@libin:~/program/C/Linux/system$ stty -a
- speed 38400 baud; rows 36; columns 134; line = 0;
- intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S;susp = ^Z;
- rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
- -parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
- -ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8
- opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
- isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
- libin@libin:~/program/C/Linux/system$ ./tsys "sleep 7"
- ^Cstatus = 2
- abnormal termination,signal number =2
- libin@libin:~/program/C/Linux/system$ sleep 7
- ^C
- libin@libin:~/program/C/Linux/system$ echo $?
- 130
- libin@libin:~/program/C/Linux/system$ ./tsys "sleep 7"
- ^\status = 3
- abnormal termination,signal number =3
- libin@libin:~/program/C/Linux/system$ sleep 7
- ^\退出
- libin@libin:~/program/C/Linux/system$ echo $?
- 131
- root@libin:~/program/C/Linux/system# ./tsys "sleep 50" &
- [1] 2518
- root@libin:~/program/C/Linux/system# ps -ef
- root 2359 2343 0 12:42 pts/0 00:00:00 /bin/bash
- root 2518 2359 0 12:55 pts/0 00:00:00 ./tsys sleep 50
- root 2519 2518 0 12:55 pts/0 00:00:00 sh -c sleep 50
- root 2520 2519 0 12:55 pts/0 00:00:00 sleep 50
- root 2521 2359 0 12:56 pts/0 00:00:00 ps -ef
- root@libin:~/program/C/Linux/system# kill -3 2520
- Quit
- status = 33536
- normal termination,exit status = 131
- root@libin:~/program/C/Linux/system# ./tsys "sleep 50" &
- [1] 2568
- root@libin:~/program/C/Linux/system# ps -ef
- root 2568 2359 0 13:01 pts/0 00:00:00 ./tsys sleep 50
- root 2569 2568 0 13:01 pts/0 00:00:00 sh -c sleep 50
- root 2570 2569 0 13:01 pts/0 00:00:00 sleep 50
- root 2571 2359 0 13:01 pts/0 00:00:00 ps -ef
- root@libin:~/program/C/Linux/system# kill -3 2569
- status = 3
- abnormal termination,signal number =3
- #define _XOPEN_SOURCE
- #include<stdio.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<signal.h>
- #include<sys/wait.h>
- void pr_exit(int status)
- {
- printf("status = %d\n",status);
- if(WIFEXITED(status))
- {
- printf("normal termination,exit status = %d\n",WEXITSTATUS(status));
- }
- else if(WIFSIGNALED(status))
- {
- printf("abnormal termination,signal number =%d%s\n",
- WTERMSIG(status),
- #ifdef WCOREDUMP
- WCOREDUMP(status)?"core file generated" : "");
- #else
- "");
- #endif
- }
- }
- int main(int argc,char* argv[])
- {
- int status;
- if(argc<2)
- {
- fprintf(stderr,"usage:tsys cmd\n");
- return -1;
- }
- if((status = system(argv[1]) )<0)
- {
- fprintf(stderr,"system error\n");
- return -2;
- }
- pr_exit(status);
- return 0;
- }
一,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; }
結果: