在C語言中調用shell命令的實現方法

1、system(執行shell 命令)
相關函數
fork,execve,waitpid,popen
表頭文件 #include<stdlib.h>
定義函數 int system(const char * string);
函數說明 system()會調用fork()產生子進程,由子進程來調用/bin/sh-c
string來執行參數string字符串所代表的命令,此命令執行完後隨
即返回原調用的進程。在調用system()期間SIGCHLD 信號會被暫時
擱置,SIGINT和SIGQUIT 信號則會被忽略。
返回值 如果system()在調用/bin/sh時失敗則返回127,其他失敗原因返回-
1。若參數string爲空指針(NULL),則返回非零值。如果system()調
用成功則最後會返回執行shell命令後的返回值,(存放在返回值的8~15位)但是此返回值也有
可能爲system()調用/bin/sh失敗所返回的127,因此最好能再檢查
errno 來確認執行成功。
附加說明 在編寫具有SUID/SGID權限的程序時請勿使用system(),system()會
繼承環境變量,通過環境變量可能會造成系統安全的問題。
範例:
複製代碼 代碼如下:

#include<stdlib.h>
main(){
system(“ls -al /etc/passwd /etc/shadow”);
}

2、popen(建立管道I/O)
相關函數
pipe,mkfifo,pclose,fork,system,fopen
表頭文件 #include<stdio.h>
定義函數 FILE * popen( const char * command,const char * type);
函數說明 popen()會調用fork()產生子進程,然後從子進程中調用/bin/sh -c
來執行參數command的指令。參數type可使用“r”代表讀取,“w”
代表寫入。依照此type值,popen()會建立管道連到子進程的標準輸
出設備或標準輸入設備,然後返回一個文件指針。隨後進程便可利
用此文件指針來讀取子進程的輸出設備或是寫入到子進程的標準輸
入設備中。此外,所有使用文件指針(FILE*)操作的函數也都可以使
用,除了fclose()以外。
返回值 若成功則返回文件指針,否則返回NULL,錯誤原因存於errno中。
錯誤代碼 EINVAL參數type不合法。
注意事項 在編寫具SUID/SGID權限的程序時請儘量避免使用popen(),popen()
會繼承環境變量,通過環境變量可能會造成系統安全的問題。
範例:
複製代碼 代碼如下:

#include<stdio.h>
main()
{
FILE * fp;
char buffer[80];
fp=popen(“cat /etc/passwd”,”r”);
fgets(buffer,sizeof(buffer),fp);
printf(“%s”,buffer);
pclose(fp);
}

執行 root :x:0 0: root: /root: /bin/bash
3、使用vfork()新建子進程,然後調用exec函數族
複製代碼 代碼如下:

#include<unistd.h>
main()
{
    char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char*) };

    if(vfork() = =0)
    {
        execv(“/bin/ls”,argv);
    }else{       
        printf(“This is the parent process\n”);
    }

}



例:
[cpp] view plain copy
  1. status = system("./test.sh");  

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

2、如何正確判斷test.sh是否正確執行?
僅判斷status是否==0?或者僅判斷status是否!=-1? 

都錯!

3、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比特位中。
備註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開始遞增。

判斷shell腳本正常執行結束的健全代碼如下:

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <sys/wait.h>  
  4. #include <sys/types.h>  
  5.   
  6. int main()  
  7. {  
  8.     pid_t status;  
  9.   
  10.   
  11.     status = system("./test.sh");  
  12.   
  13.     if (-1 == status)  
  14.     {  
  15.         printf("system error!");  
  16.     }  
  17.     else  
  18.     {  
  19.         printf("exit status value = [0x%x]\n", status);  
  20.   
  21.         if (WIFEXITED(status))  
  22.         {  
  23.             if (0 == WEXITSTATUS(status))  
  24.             {  
  25.                 printf("run shell script successfully.\n");  
  26.             }  
  27.             else  
  28.             {  
  29.                 printf("run shell script fail, script exit code: %d\n", WEXITSTATUS(status));  
  30.             }  
  31.         }  
  32.         else  
  33.         {  
  34.             printf("exit status = [%d]\n", WEXITSTATUS(status));  
  35.         }  
  36.     }  
  37.   
  38.     return 0;  
  39. }  

WIFEXITED(stat_val) Evaluates to a non-zero value if status
was returned for a child process that
terminated normally.

WEXITSTATUS(stat_val) If the value of WIFEXITED(stat_val) is
non-zero, this macro evaluates to the
low-order 8 bits of the status argument
that the child process passed to _exit()
or exit(), or the value the child
process returned from main().


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章