一、system函数说明
二、system函数使用
三、popen函数说明
四、popen函数使用
五、popen函数对比system函数的好处
六、exec族函数、system()函数和popen()函数区别

一、system函数说明

system函数定义及函数原型

system() 函数是一个C标准库函数,用于执行系统命令或外部程序。它允许你从C程序中调用命令行命令。system() 函数的原型如下:

NAME
       system - execute a shell command

SYNOPSIS
       #include <stdlib.h>

       int system(const char *command);

command 是一个以空终止的C字符串,包含要执行的命令。
system() 函数的工作方式如下:

它启动一个新的shell进程(通常是默认的命令行shell)。
然后在新的shell中执行command字符串中指定的命令。
当命令执行完毕后,system() 函数等待命令的返回状态(退出状态)。
最后,它返回命令的返回状态。
下面是一个简单的示例,演示如何使用system()函数来运行系统命令:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int result = system("ls -l");  // 在Unix/Linux系统上列出当前目录的文件
    if (result == -1) {
        perror("system");
    } else {
        printf("Command executed with return status: %d\n", result);
    }

    return 0;
}

这个程序将执行ls -l命令,并打印命令的返回状态。请注意,system()函数允许你运行外部命令,但要小心不要执行不受信任的或未经验证的命令,以避免安全风险。

system()函数通常用于执行命令行工具或执行外部脚本等任务。

源码

system()函数功能强大,我对linux中的实现比较了解,具体分析这个,windows中的类似就不详解了。

好了,先看linux版system函数的源码:

#include
#include
#include
#include

int system(const char * cmdstring)
{
  pid_t pid;
  int status;

  if(cmdstring == NULL){
      
      return (1);
  }


  if((pid = fork())<0){

        status = -1;
  }
  else if(pid == 0){
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    -exit(127); //子进程正常执行则不会执行此语句
    }
  else{
        while(waitpid(pid, &status, 0) < 0){
          if(errno != EINTER){
            status = -1;
            break;
          }
        }
    }
    return status;
}

先分析一下原理,然后再看上面的代码大家估计就能看懂了:

当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的 pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring,(char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个 shell进程,这个shell的参数是cmdstring,就是system接受的参数。在windows中的shell是command,想必大家很熟悉shell接受命令之后做的事了。

如果上面的你没有看懂,那我再解释下fork的原理:当一个进程A调用fork时,系统内核创建一个新的进程B,并将A的内存映像复制到B的进程空间中,因为A和B是一样的,那么他们怎么知道自己是父进程还是子进程呢,看fork的返回值就知道,上面也说了fork在子进程中返回0,在父进程中返回子进程的pid。

windows中的情况也类似,就是execl换了个又臭又长的名字,参数名也换的看了让人发晕的,我在MSDN中找到了原型,给大家看看:

HINSTANCE   ShellExecute(
          HWND   hwnd,
          LPCTSTR   lpVerb,
          LPCTSTR   lpFile,
          LPCTSTR   lpParameters,
          LPCTSTR   lpDirectory,
          INT   nShowCmd
  );   

用法如下:

ShellExecute(NULL, “open”, “c:\a.reg”, NULL, NULL, SW_SHOWNORMAL);

你也许会奇怪 ShellExecute中有个用来传递父进程环境变量的参数 lpDirectory,linux中的execl却没有,这是因为execl是编译器的函数(在一定程度上隐藏具体系统实现),在linux中它会接着产生一个linux系统的调用execve, 原型见下:
int execve(const char * file,const char **argv,const char **envp);

看到这里你就会明白为什么system()会接受父进程的环境变量,但是用system改变环境变量后,system一返回主函数还是没变,这就是我在 22楼反复强调的。原因从system的实现可以看到,它是通过产生新进程实现的,从我的分析中可以看到父进程和子进程间没有进程通信,子进程自然改变不了父进程的环境变量。希望小菜们不要拿tc或使用tc库的其他编译器中的system的调用结果来反驳我,这不是一个概念,DOS早死翘翘了,玩 linux吧。就说到这里了。

system(执行shell 命令)

  1. 相关函数
    fork,execve,waitpid,popen
  2. 表头文件
    #include<stdlib.h>
  3. 定义函数
    int system(const char * string);
  4. 函数说明
    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()会继承环境变量,通过环境变量可能会造成系统安全的问题。

system()函数的返回值如下:

成功,则返回进程的状态值;当sh不能执行时,返回127;失败返回-1;

二、system函数使用

demo27.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
// 函数原型 int system(const char *command);

int main()
{
	pid_t pid;
	int changedata = 10;
	
	while(1){
		printf("please input a changedata.\n");
		scanf("%d",&changedata);
		if(changedata == 1){
			int fdSrc;

			pid = fork();

			if(pid > 0){
				wait(NULL);
			}

			if(pid == 0){
				//execl("./changeData","changeData","protest.txt",NULL);
				system("./changeData protest.txt");
			}
		}
		else{
                	printf("wait,do nothing\n");
        	}
	}

	return 0;
}

changeData.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

//整数类型主函数(整数类型统计参数个数,字符类型指针数组指向字符串参数)
int main(int argc,char **argv)
{
	int fdSrc;
	char *readBuf = NULL;

	//参数错误
	if(argc != 2){
		printf("param error\n");
		exit(-1);
	}
	
	//打开原文件Src	
	fdSrc = open(argv[1],O_RDWR);
	
	//计算文件大小并让光标回到头
	int size = lseek(fdSrc,0,SEEK_END);
	lseek(fdSrc,0,SEEK_SET);

	//开辟空间给buf并读取原文件Src
	readBuf = (char *)malloc(sizeof(char)*size + 8);
	int n_read = read(fdSrc,readBuf,size);

	//查找并修改数据
//char *strstr(const char *haystack, const char *needle);
	char *p = strstr(readBuf,"LENGTH=");
	if(p == NULL){
		printf("not found\n");
		exit(-1);
	}

	p = p+strlen("LENGTH=");
	*p = '5';
	lseek(fdSrc,0,SEEK_SET);

	int n_write = write(fdSrc,readBuf,strlen(readBuf));

	//关闭文件
	close(fdSrc);

	return 0;
}

运行结果

请添加图片描述

demo28.c

//文件execle.c(demo28.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int system(const char *command);

int main(void)
{
	printf("before this process get system ps\n");

	if(system("ps") == -1)
	{
		printf("system failed!\n");

		perror("why failed");
	}       
    printf("after system\n");
    return 0;
}

运行结果

请添加图片描述

三、popen函数说明

popen函数定义及函数原型

popen 是一个标准 C 库函数,通常用于在程序中执行一个外部命令并建立与该命令的标准输入或输出之间的管道连接。这允许你与外部进程进行交互,发送输入并读取输出。popen 函数在 POSIX 操作系统上可用,例如 Linux。

以下是 popen 函数的原型:

NAME
       popen, pclose - pipe stream to or from a process

SYNOPSIS
       #include <stdio.h>

       FILE *popen(const char *command, const char *type);

       int pclose(FILE *stream);

command 是一个字符串,表示要执行的外部命令,例如 “ls -l” 或 “grep search_string”。
mode 是一个字符串,指定打开流的模式。通常使用 “r” 用于读取外部命令的输出,或使用 “w” 用于向外部命令发送输入。
popen 函数返回一个 FILE* 指针,可以像文件流一样用于读取或写入数据。如果调用成功,该指针可以用于执行与外部命令的标准输入或输出之间的通信。如果出现错误,popen 将返回 NULL。

  1. 参数说明:

command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。

mode: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

  1. 返回值:

如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL,具体错误要根据errno判断

int pclose (FILE* stream)

参数说明:

stream:popen返回的文件指针

返回值:

如果调用失败,返回 -1

  1. 作用:

popen() 函数用于创建一个管道:其内部实现为调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程这个进程必须由 pclose() 函数关闭

通常,你可以使用 popen 执行外部命令,然后通过读取或写入 FILE* 指针来与该命令进行交互。当你不再需要与外部命令的交互时,应使用 pclose 函数来关闭与外部命令的管道连接。

以下是一个示例,演示如何使用 popen 执行外部命令并读取其输出:

#include <stdio.h>

int main() {
    FILE *fp;
    char buffer[1024];

    // 执行外部命令
    fp = popen("ls -l", "r");
    if (fp == NULL) {
        perror("popen");
        return 1;
    }

    // 读取命令的输出
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        printf("%s", buffer);
    }

    // 关闭与外部命令的管道连接
    pclose(fp);

    return 0;
}

上述示例执行 ls -l 命令,并将其输出读取到程序中,然后关闭管道连接。

四、popen函数使用

popen执行完后,读取内容

#include <stdio.h>
#include <string.h>
int main(void)
{
    FILE *fp = NULL;
    char buf[10240] = {0};
    fp = popen("ls -al","r");
    if(fp == NULL){
        return 0;
    }
    fread(buf, 10240, 1, fp);
    printf("%s\n",buf);
    pclose(fp);
    return 0;
}

popen执行完后,仍然可以向管道继续传送命令,实现功能

#include <stdio.h>
#include <string.h>
int main(void)
{
    FILE *fp = NULL;
    char buf[10240] = {0};
    fp = popen("mysql -u root -p123456 my_db","w");
    if(fp == NULL){
        return 0;
    }
    fprintf(fp,"delete from my_test_table;");
    fprintf(fp,"\\q");
    pclose(fp);
    return 0;
}

五、popen函数对比system函数的好处

比system在应用中的好处:可以获取运行的输出结果

//文件demo29.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int system(const char *command);

int main(void)
{
	char ret[1024] = {0};

	system("ps"); 
	printf("system ret = %s\n",ret);

    return 0;
}

运行结果

请添加图片描述

//文件demo30.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:FILE *popen(const char *command, const char *type);
//		int pclose(FILE *stream);

int main(void)
{
	FILE *fp;
	char ret[1024] = {0};

	fp = popen("ps","r");
	int n_read = fread(ret,1,1024,fp);

	printf("popen read ret %d byte,ret= %s\n",n_read,ret);

	return 0;
}

运行结果

请添加图片描述

六、exec族函数、system()函数和popen()函数区别

exec函数族、system()函数和popen()函数都与执行外部命令或程序有关,但它们在用途和用法上有一些重要的区别。

  1. exec函数族:
  • 用途:exec函数族用于在当前进程中执行其他程序,通常是替代当前进程的映像。主要用于在同一进程内启动新程序,替换当前进程的代码和数据。
  • 包括函数:exec函数族包括一系列函数,如execve、execl、execv等,用于不同的参数传递方式。
  • 用法:exec函数族通常需要在父进程中先调用fork()创建一个子进程,然后在子进程中使用exec函数执行其他程序。这样可以保留父进程的状态并在子进程中切换到新的程序。
  1. system()函数:
  • 用途:system()函数用于执行系统命令或外部程序,它启动一个新的shell进程,然后在该shell中执行指定的命令。主要用于执行简单的系统命令或外部程序。
  • 使用:system()函数非常简单,只需传递要执行的命令字符串即可。它不需要调用fork()或exec(),并且返回命令的退出状态。
  1. popen()函数:
  • 用途:popen()函数用于打开一个管道并与外部命令建立双向通信。它可以用于在C程序中与外部命令进行输入和输出交互,类似于管道。
  • 使用:popen()函数返回一个文件指针,可以用于向外部命令发送输入并从命令接收输出。它允许在C程序中与外部命令进行复杂的通信,而不仅仅是简单地执行命令。

总结:
如果你需要在同一进程中执行其他程序,通常使用exec函数族,结合fork来创建子进程。
如果你只需要执行系统命令或外部程序,并不需要与其进行输入/输出交互,可以使用system()函数。
如果需要与外部命令进行输入和输出交互,可以使用popen()函数。

Logo

更多推荐