之前寫Linux應用程式的時候,最喜歡使用system命令了,後來發現這個命令使用需要很謹慎。之前使用該命令來進行MD5校驗,通過返回值來判斷校驗是否成功不夠嚴謹。有時候因為system呼叫MD5sum檔案不存在導致的錯誤,應用並不能夠直觀發現。反而一直在md5校驗碼上花費太多心思。於是打算重寫一下system函式來玩玩。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define BUF_SIZE 100
/*******************************************************************
** 函式名: my_system
** 函式描述: shell命令執行判斷,儲存執行結果。
** 引數: cmd: shell命令;result: shell命令執行結果
** 返回: shell命令執行失敗返回-1;執行成功返回shell命令結束碼
********************************************************************/
int my_system(char *cmd, char *result)
{
int rc = 0;
int ret = -1;
FILE *fp = NULL;
strcat(cmd, " 2>&1");
fp = popen(cmd, "r");
if (NULL == fp) {
perror("popen error\n");
goto EXIT;
}
while (NULL != fgets(result, BUF_SIZE, fp)) {
/* 去除換行符 */
if ('\n' == result[strlen(result)-1]) {
result[strlen(result)-1] = '\0';
}
}
rc = pclose(fp);
if (-1 == rc) {
perror("pclose error\n");
goto EXIT;
}
printf("子程式結束狀態 = %d\n", rc);
if (!WIFEXITED(rc)) {
perror("Run command failed\n");
goto EXIT;
} else {
ret = WEXITSTATUS(rc);
}
EXIT:
if (NULL == fp || -1 == rc) {
strncpy(result, strerror(errno), BUF_SIZE);
//printf("errno = %s\n", strerror(errno));
}
fp = NULL;
return ret;
}
void main()
{
char cmd[BUF_SIZE] = {0};
char result[BUF_SIZE] = {0};
int cmd_ret;
snprintf(cmd, BUF_SIZE, "diff a b");
cmd_ret = my_system(cmd, result);
printf("命令返回值 = %d\n", cmd_ret);
if (0 != cmd_ret) {
printf("failed reason :[%s]\n", result);
} else {
printf("success\n");
}
}
複製程式碼
執行結果:
子程式結束狀態 = 512
命令返回值 = 2
failed reason :[diff: a: No such file or directory]
複製程式碼
程式碼知識點解讀
一、2>&1 | tee
1、2>&1是什麼意思? 0 stdin,1 stdout,2 stderr 標準輸入 -》 鍵盤 標準輸出 -》 螢幕
2>&1應該分成兩個部分來看:
- 2>作用將標準出錯重定向到某個特定的地方;
- &1指無論標準輸出在哪裡。
2>&1的意思就是說無論標準出錯在哪裡(哪怕是沒有),都將標準出錯重定向到標準輸出中。
2、 | 管道符 管道的作用是提供一個通道,將上一個程式的標準輸出重定向到下一個程式作為下一個程式的標準輸入。 通常使用管道的好處是一方面形式上簡單,另一方面其執行效率要遠高於使用臨時檔案。
3、tee的作用 tee從標準輸入中讀取,並將讀入的內容寫到標準輸出以及檔案中,配合管道符使用。 make kernel 2>&1 | tee -a kernel.log (- a表示追加到檔案末尾) 標準輸出的快取有限制,因編譯核心產生的log資訊很多,因此通過tee來儲存到檔案中。
二、popen
1、函式說明 popen()函式通過建立一個管道,呼叫fork()產生一個子程式,執行一個shell以執行命令來開啟一個程式。這個管道必須由pclose()函式關閉,而不是fclose()函式。pclose()函式關閉標準I/O流,等待命令執行結束,然後返回shell的終止狀態。如果shell不能被執行,則pclose()返回的終止狀態與shell已執行exit一樣。
type引數只能是讀或者寫中的一種,得到的返回值(標準I/O流)也具有和type相應的只讀或只寫型別。如果type是"r"則檔案指標連線到command的標準輸出;如果type是"w"則檔案指標連線到command的標準輸入。
command引數是一個指向以NULL結束的shell命令字串的指標。這行命令將被傳到bin/sh並使用-c標誌,shell將執行這個命令。
popen()的返回值是個標準I/O流,必須由pclose來終止。前面提到這個流是單向的(只能用於讀或寫)。向這個流寫內容相當於寫入該命令的標準輸入,命令的標準輸出和呼叫popen()的程式相同;與之相反的,從流中讀資料相當於讀取命令的標準輸出,命令的標準輸入和呼叫popen()的程式相同。
2、返回值 如果呼叫fork()或pipe()失敗,或者不能分配記憶體將返回NULL,否則返回標準I/O流。popen()沒有為記憶體分配失敗設定errno值。如果呼叫fork()或pipe()時出現錯誤,errno被設為相應的錯誤型別。如果type引數不合法,errno將返回EINVAL。
3、狀態判斷 子程式的結束狀態返回後存於status,可通過巨集判斷結束情況 。
- WIFEXITED(status)如果子程式正常結束則為非0值。
- WEXITSTATUS(status)取得子程式exit()返回的結束程式碼,一般會先用WIFEXITED 來判斷是否正常結束才能使用此巨集。
- WIFSIGNALED(status)如果子程式是因為訊號而結束則此巨集值為真 。
- WTERMSIG(status)取得子程式因訊號而中止的訊號程式碼,一般會先用WIFSIGNALED 來判斷後才使用此巨集。
- WIFSTOPPED(status)如果子程式處於暫停執行情況則此巨集值為真。一般只有使用WUNTRACED 時才會有此情況。
- WSTOPSIG(status)取得引發子程式暫停的訊號程式碼,一般會先用WIFSTOPPED 來判斷後才使用此巨集。
三、實際測試
1、a b檔案內容不相同時
執行結果:
子程式結束狀態 = 256
命令返回值 = 1
failed reason :[> 不同的地方]
複製程式碼
2、a b檔案內容相同時
執行結果:
子程式結束狀態 = 0
命令返回值 = 0
success
複製程式碼
3、a或者b檔案不存在時
執行結果:
子程式結束狀態 = 512
命令返回值 = 2
failed reason :[diff: a: No such file or directory]
複製程式碼
4、diff bin檔案不存在時
執行結果:
子程式結束狀態 = 32512
命令返回值 = 127
failed reason :[sh: 1: diff: not found]
複製程式碼
四、參考資料
Linux下system與popen函式 - u013485792的專欄 - CSDN部落格 https://blog.csdn.net/u013485792/article/details/52525702
Linux下使用popen()執行shell命令 - 功夫Panda - 部落格園 http://www.cnblogs.com/caosiyang/archive/2012/06/25/2560976.html
linux system函式是否執行成功判斷方法 - weixin_39020720的部落格 - CSDN部落格 https://blog.csdn.net/weixin_39020720/article/details/80917126