介紹一下Linux系統中的代碼執行shell等命令的幾種操作方式:
一、標准流管道popen
該函數的原型是FILE * popen(const char* command, const char *type);
command:使我們要執行的命令,即上述的運行命令,
type:有兩種可能的取值,“r”(代表讀取)或者“w"(代表寫入)
popen()會調用fork()產生子進程,然后從子進程中調用/bin/sh-c來執行參數command的指令,參數type可使用“r”讀取 或者“w”寫入,根據type的值,popen()會創建管道連接到子進程的標准輸出設備或者標准輸入設備,然后返回一個文件指針。隨后進程就可以利用此文件指針來讀取子進程的標准輸出設備或者寫入子進程的標准輸入設備。
這個函數可以大大減少代碼的編寫量,但使用不太靈活,不能自己創建管道那么靈活,並且popen()必須使用標准的I/o函數進行操作,也不能使用read(),wirte()這種不帶緩沖的I/O函數,必須使用pclose()來關閉管道流,該函數關閉標准I/O流,並等待命令執行結束
個人總結:記住,向這個流中寫內容相當於寫入該命令(或者是該程序)標准輸入; 向這個流中讀數據相當於讀取該命令(或者是該程序)的標准輸出
寫例子:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
int main()
{
FILE *fp;
char *cmd="cat > aaa"; ///該命令新建一個文件,提示用戶輸入數據作為其內容
char buf[1000]="ni hao wo de peng you!\n"; ///待輸入的參數
if((fp=popen(cmd,"w"))==NULL)
return 1;
fwrite(buf,sizeof(char),1000,fp); ///通過文件描述符寫入文件數據
pclose(fp); ///關閉文件描述符
exit(0);
return 0;
}
讀例子:
LD_S32 RunSysCmd2(LD_CS8 *pCmd, LD_S8 *pRslBuf, LD_S32 bufSz)
{
LD_S32 ret = LD_FAILURE;
FILE *pFd = popen(pCmd, "r");//創建一個流管道生成一個子進程用於執行命令
if(pFd)
{
memset(pRslBuf, 0, bufSz);
if(fread(pRslBuf, bufSz - 1, 1, pFd) >= 0)//從該命令中讀取數據
{
ret = LD_SUCCESS;
}
pclose(pFd);//關閉流管道
}
return ret;
}
二、system
system("cat "Read pipe successfully!" > test1")
三、exec
在Linux中,並不存在exec()函數,exec指的是一組函數,一共有6個,分別是:
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
其中只有execve是真正意義上的系統調用,其它都是在此基礎上經過包裝的庫函數。
exec函數族的作用是根據指定的文件名找到可執行文件,並用它來取代調用進程的內容,換句話說,就是在調用進程內部執行一個可執行文件。這里的可執行文件既可以是二進制文件,也可以是任何Linux下可執行的腳本文件。
exec和source都屬於bash內部命令(builtins commands),在bash下輸入man exec或man source可以查看所有的內部命令信息。
bash shell的命令分為兩類:外部命令和內部命令。外部命令是通過系統調用或獨立的程序實現的,如sed、awk等等。內部
命令是由特殊的文件格式(.def)所實現,如cd、history、exec等等。
在說明exe和source的區別之前,先說明一下fork的概念。
fork是linux的系統調用,用來創建子進程(child process)。子進程是父進程(parent process)的一個副本,從父進程那里
獲得一定的資源分配以及繼承父進程的環境。子進程與父進程唯一不同的地方在於pid(process id)。
環境變量(傳給子進程的變量,遺傳性是本地變量和環境變量的根本區別)只能單向從父進程傳給子進程。不管子進程的環境
變量如何變化,都不會影響父進程的環境變量。
shell script:
有兩種方法執行shell scripts,一種是新產生一個shell,然后執行相應的shell scripts;一種是在當前shell下執行,不
再啟用其他shell。
新產生一個shell然后再執行scripts的方法是在scripts文件開頭加入以下語句
#!/bin/sh
一般的script文件(.sh)即是這種用法。這種方法先啟用新的sub-shell(新的子進程),然后在其下執行命令。
另外一種方法就是上面說過的source命令,不再產生新的shell,而在當前shell下執行一切命令。
source:
source命令即點(.)命令。
在bash下輸入man source,找到source命令解釋處,可以看到解釋”Read and execute commands from filename in the
current shell environment and …”。從中可以知道,source命令是在當前進程中執行參數文件中的各個命令,而不是另起子
進程(或sub-shell)。
exec:
在bash下輸入man exec,找到exec命令解釋處,可以看到有”No new process is created.”這樣的解釋,這就是說exec命
令不產生新的子進程。那么exec與source的區別是什么呢?
exec命令在執行時會把當前的shell process關閉,然后換到后面的命令繼續執行。
系統調用exec是以新的進程去代替原來的進程,但進程的PID保持不變。因此,可以這樣認為,exec系統調用並沒有創建新的
進程,只是替換了原來進程上下文的內容。原進程的代碼段,數據段,堆棧段被新的進程所代替。
popen,system和exec區別:
1.system和popen都是執行了類似的運行流程,大致是fork->execl->return。但是我們看到system在執行期間調用進程會一直等待shell命令執行完成(waitpid等待子進程結束)才返回,但是popen無須等待shell命令執行完成就返回了。我們可以理解system為串行執行,在執行期間調用進程放棄了”控制權”,popen為並行執行。
2.popen中的子進程沒人給它”收屍”了啊?是的,如果你沒有在調用popen后調用pclose那么這個子進程就可能變成”僵屍”。
3.對於管道已經很清楚,而管道寫可能用的地方比較少。而對於寫可能更常用的是system函數:
system("cat "Read pipe successfully!" > test1")
4.如果不需要使用到程序的I/O數據流,那么system是最方便的。
而且system函數是C89和C99中標准定義的,可以跨平台使用。而popen是Posix 標准函數,可能在某些平台無法使用(windows應該是可以的吧,沒做過測試)。
5.可以看出,popen可以控制程序的輸入或者輸出,而system的功能明顯要弱一點,比如無法將讀取結果用於程序中。
6.如果上述兩個函數還無法滿足你的交互需求,那么可以考慮exec函數組了。
system是用shell來調用程序=fork+exec+waitpid,而exec是直接讓你的程序代替原來的程序運行。
system 是在單獨的進程中執行命令,完了還會回到你的程序中。而exec函數是直接在你的進程中執行新的程序,新的程序會把你的程序覆蓋,除非調用出錯,否則你再也回不到exec后面的代碼,就是說你的程序就變成了exec調用的那個程序了。
最后說一下exit函數:
exit是一個庫函數,exit(1)表示發生錯誤后退出程序, exit(0)表示正常退出。
對你的程序來說,沒有區別。對使用你的程序的人或者程序來說,區別可就大了。
一般來說,exit 0 可以告知你的程序的使用者:你的程序是正常結束的。如果 exit 非 0 值,那么你的程序的使用者通常會認為你的程序產生了一個錯誤。
以 shell 為例,在 shell 中調用完你的程序之后,用 echo $? 命令就可以看到你的程序的 exit 值。在 shell 腳
本中,通常會根據上一個命令的 $? 值來進行一些流程控制。
同樣的情形出現在 C 語言的 exec 系列函數中。
例:
當你 exit 0 的時候,在調用環境 echo $? 就返回0,也就是說調用環境就認為你的這個程序執行正確
當你 exit 1 的時候,一般是出錯定義這個1,也可以是其他數字,很多系統程序這個錯誤編號是有約定的含義的。
但不為0 就表示程序運行出錯。 調用環境就可以根據這個返回值判斷 你這個程序運行是否ok。搜索如果你用 腳本 a 調用 腳本b ,要在a中判斷b是否正常返回,就是根據 exit 0 or 1 來識別。執行完b后, 判斷 $? 就是返回值