[shell]C语言调用shell脚本接口
1) system(shell命令或shell腳本路徑);
? ? 執行過程:system()會調用fork()產生子進程,由子進程來調用/bin/sh -c string來執行參數string字符串所代表的的命令,此命令執行完后隨即返回原調用的進程。在調用system()期間SIGCHLD信號會被暫時擱置,SIGINT和SIGQUIT信號會被忽略。
? ? 返回值:如果system()在調用/bin/sh時失敗則返回127,其他失敗原因返回-1.若參數string為空指針(NULL),則返回非零值。如果system()調用成功則最后會返回執行shell命令后的返回值,但是此返回值也有可能為system()調用/bin/sh失敗所返回的127,因此最好能在檢查errno來確認執行成功。
? ? ?注意: 在編寫具有SUID/SGID權限的程序時,盡量避免使用system(),system()會繼承環境變量,通過環境變量可能會造成系統安全的問題。
例子:在~/myprogram/目錄下有shell腳本test.sh,內容為:
#!bin/bash
#test.sh
echo $HOME
在該目錄下新建一個c文件systemtest.c,內容為:
#include /*This program is used to test function system*/ void main() {int rv = system("~/myprogram/test.sh");if (WIFEXITED(rv)) {printf("subprocess exited, exit code: %d\n", WEXITSTATUS(rv));if (0 == WEXITSTATUS(rv)) {//if command returning 0 means succeedprintf("command succeed");} else {if (127 == WEXITSTATUS(rv)) {printf("command not found\n");return WEXITSTATUS(rv);} else {printf("command failed: %d\n", strerror(WEXITSTATUS(rv)));return WEXITSTATUS(rv);}}} else {printf("subprocess exit failed");return -1;} }? 執行結果如下:
xiakeyou@ubuntu:~/myprogram$ gcc systemtest.c -o systemtest
xiakeyou@ubuntu:~/myprogram$ ./systemtest
/home/d/e/xiakeyou
xiakeyou@ubuntu:~/myprogram$
2) popen(char *command, char *type)
? ? 執行過程: popen()會調用fork()產生子進程,然后從子進程中調用/bin/sh -c來執行參數command的指令。參數type可使用"r"代表讀取,"w"代表寫入。依照此type值,popen()會建立管道連到子進程的標準輸出設備或標準輸入設備,然后返回一個文件指針。隨后進程便可利用此文件指針來讀取子進程的輸出設備或是寫入到子進程的標準輸入設備中。此外,所有使用文件指針(FILE *)操作的函數也都可以使用,除了fclose()以外。
? ? 返回值: 若成功則返回文件指針,否則返回NULL,錯誤愿意存于errno中。
? ? 另外可以使用pclose捕捉該條命令有沒有正確被執行,只是pclose不能在popen后面太快不執行,否則可能pipe error。
? However,pclose(3) returns the exit value of the process that you were running. Non-zero exit processes are typical of programs to signal error conditions on exit, and some programs are even nice enough to signal to you what the error was based on the exit value.?
? ? ?注意: 在編寫具SUID/SGID權限的程序時請盡量避免使用popen(),popen()會繼承環境變量,通過環境變量可能會造成系統安全的問題。
例子:C程序popentest.c內容如下:
#include main() {FILE *fp;char buffer[80];fp = popen("~/myprogram/test.sh", "r");fgets(buffer, sizeof(buffer), fp);printf("%s", buffer);pclose(fp); }執行結果如下:
xiakeyou@ubuntu:~/myprogram$ vim popentest.c xiakeyou@ubuntu:~/myprogram$ gcc popentest.c -o popentest xiakeyou@ubuntu:~/myprogram$ ./popentest /home/d/e/xiakeyou xiakeyou@ubuntu:~/myprogram$popen函數執行命令后,返回一個指向該命令輸出的文件句柄,接下來就可以用fgets等文件操作函數去讀取輸出結果。
#include?
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
type的參數只能是"r"或"w"
例如:
#include #include int main(int argc, char *argv[]){FILE *fstream = NULL;char buff[1024];memset(buff, 0, sizeof(buff));if (NULL == (fstream=popen("ls -l", "r"))) {fprintf(stderr, "execute command failed: %s", strerror(errno));return -1;}if (NULL != fgets(buff, sizeof(buff), fstream)) {printf("%s", buff);} else {pclose(fstream);return -1;}pclose(fstream);return 0; }3) linux exec的用法
說是exec系統調用,實際上在linux中,并不存在一個exec()的函數形式,exec指的是一組函數,一共有6個,分別是:
#include
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函數族的函數執行成功后不會返回,因為調用進程的實體,包括代碼段,數據段和堆棧等都已經被新的內容取代,只留下進程ID等一些表面上的信息仍保持原樣,頗有些神似"三十六計"中的“金蟬脫殼”。看上去還是舊的軀殼,卻已經注入了新的靈魂。只有調用失敗了,它們才會返回一個-1,從原程序的調用點接著往下執行。
現在我們應該明白了,Linux下是如何執行新程序的,每當有進程認為自己不能為系統和用戶做出任何貢獻了,他就可以發揮最后一點余熱,調用任何一個exec,讓自己以新的面貌重生;或者,更普遍的情況是,如果一個進程想執行另一個程序,他就可以fork出一個新進程,然后調用任何一個exec,這樣看起來就好像通過執行應用程序而產生了一個新進程一樣。
事實上第二種情況被應用的如此普遍,以至于Linux專門為其做了優化,我們已經知道,fork會將調用級才能拿的所有內容原封不動的拷貝到新產生的子進程中去,這些拷貝的動作很消耗時間,而如果fork完之后我們馬上就調用exec,這些辛辛苦苦拷貝來的東西又會被立刻抹掉,這看起來非常不劃算,于是人們設計了一種"寫時拷貝(copy-on-write)"技術,使得fork結束后并不立刻復制父進程的內容,而是到了真正實用的時候才復制,這樣如果下一條語句是exec,它就不會白白作無用功了,也就提高了效率。
返回值
如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存于errno中。
大家在平時的編程中,如果用到了exec函數族,一定記得要加錯誤判斷語句。因為與其他系統調用比起來,exec很容易受傷,被執行文件的位置,權限等很多因素都能導致該調用的失敗。最常見的錯誤是:
- 找不到文件或路徑,此時errno被設置為ENOENT;
- 數組argv和envp忘記用NULL結束,此時errno被設置為EFAULT;
- 沒有對要執行文件的運行權限,此時errno被設置為EACCES。
函數族的意義
- ?l 表示以參數列表的形式調用
- ?v 表示以參數數組的方式調用
- ?e 表示可傳遞環境變量
- ?p 表示PATH中搜索執行的文件,如果給出的不是絕對路徑就會去PATH搜索相應名字的文件,如PATH沒有設置,則會默認在/bin,/usr/bin下搜索。
另:調用時參數必須以NULL結束。原進程打開的文件描述符是不會在exec中關閉的,除非用fcntl設置它們的“執行時關閉標志(close on exec)”而原進程打開的目錄流都將在新進程中關閉。
例子:
#include int main(int argc, char *argv[]) {char *envp[] = {"PATH=/tmp", "USER=lei", "STATUS=testing", NULL};char *argv_execv[] = {"echo", "executed by execv", NULL};char *argv_execvp[] = {"echo", "executed by execvp", NULL};char *argv_execve[] = {"env", NULL};if (fork() == 0) {if (execvl("/bin/echo", "echo", "executed by execl", NULL) < 0)perror("Err on execl");}if (fork() == 0) {if (execlp("echo", "echo", "executed by execlp", NULL) < 0) perror("Err on execlp");}if (fork() == 0) {if (execle("/usr/bin/env", "env", NULL, envp) < 0) perror("Err on execle");}if (fork() == 0) {if (execv("/bin/echo", argv_execv) < 0) perror("Err on execv");}if (fork() == 0) {if (execvp("echo", argv_execvp) < 0)perror("Err on execvp");}if (fork() == 0) {if (execve("/usr/bin/env", argv_execve, envp) < 0) perror("Err on execve");} }?
總結
以上是生活随笔為你收集整理的[shell]C语言调用shell脚本接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS转义字符对照表
- 下一篇: Linux的system和popen的差