在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。


文章目录

  • 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族函数自己实现。