在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
文章目录
- 1 system
- 2 popen
- 3 exec
- 4 该选择哪种方式
在Linux上编写C代码经常会用到shell指令。常用的有三种方式,我们一一道来。
1 system
最简单的方式就是直接调用system接口,该接口返回-1表示调用shell指令失败,返回127表示调用/bin/sh失败,命令执行成功返回0。
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int ret = 0;
6
7 ret = system("touch test");
8 printf("cmd ret = %d.\n", ret);
9
10 return 0;
11 }
事实上事情并没有这么简单,在调用system期间,SIGINT和SIGQUIT会被忽略,这就要求尽量不要在死循环中调用system,否则就一定要对返回值做相应处理,man手册中有代码示例。
此外,还需要注意对SIGCHLD的处理。
system的实现主要是三步:
- 调用fork创建子进程。
- 调用execl函数运行shell程序解析指令并执行。
- 调用waitpid函数等待给子进程收尸。
由于waitpid函数执行成功的前提是对SIGCHLD的处理不能是SIGIGN,否则会产生ECHILD错误。
所以,最保险的做法如下:
1 #include <stdio.h>//erold_sighandler = signal(SIGCHLD, SIG_DFL);
2 #include <signal.h>
3
4 typedef void (*sighandler_t)(int);
5
6
7 int main(void)
8 {
9 int ret = 0;
10 sighandler_t old_sighandler;
11
12 //old_sighandler = signal(SIGCHLD, SIG_IGN); //err
13 old_sighandler = signal(SIGCHLD, SIG_DFL);
14 if (SIG_ERR == old_sighandler)
15 return -1;
16 ret = system("touch test");
17 if (0 != ret) {
18 printf("cmd ret = %d.\n", ret);
19 perror("");
20 return -2;
21 }
22 signal(SIGCHLD, old_sighandler);
23
24 return 0;
25 }
2 popen
第二种方式就是使用popen。该函数内部也会调用fork产生子进程,然后在子进程中通过exec运行/bin/sh。比起system来更灵活一些,可以创建管道,与子进程进行通信。但是,本质上和system的实现是一致的。
1 #include <stdio.h>
2
3 int main(void)
4 {
5 FILE *pfile = NULL;
6 char buff[500] = {0};
7
8 pfile = popen("touch test.txt", "w");
9 pclose(pfile);
10
11 pfile = popen("ls -al", "r");
12 if (NULL == pfile)
13 return -1;
14 fgets(buff, sizeof(buff), pfile);
15
16 printf("%s");
17
18 pclose(pfile);
19
20 return 0;
21 }
3 exec
前两种方式其实都是本种方式的封装,用起来更方便而已。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4
5 int main(void)
6 {
7 int child_pid = 0;
8 char * argv[] = {"sh", "-c" , "ls -al .", NULL};
9
10 if (0 == vfork()) {//child process
11
12 if (execv("/bin/sh", argv) < 0) {
13 perror("");
14 exit(-1);
15 }
16
17 } else { //parent
18 wait(&child_pid);
19 printf("child pid done.\n");
20 }
21
22 return 0;
23 }
4 该选择哪种方式
最简单的方式就是使用system接口,但是需要注意对返回值的判断和errno的解析。如果想和子进程进行通信(比如想拿到指令执行后的输出),就考虑使用popen方式,但由于子进程直接继承了父进程的环境变量,据说是不安全的(对于系统安全这块还没有研究,感兴趣的可以深入下)。如果是读执行效率有一定要求(vfork比fork执行效率高),或者想改造/扩展上述接口功能,则可以使用exec族函数自己实现。