system()
我正在尝试通过- 例如,启动外部应用程序system("ls")
。我想在它发生时捕获它的输出,以便我可以将它发送到另一个函数进行进一步处理。在 C/C++ 中做到这一点的最佳方法是什么?
10 回答
从弹出手册:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
试试 popen() 函数。它执行一个命令,如 system(),但将输出定向到一个新文件。返回指向流的指针。
FILE *lsofFile_p = popen("lsof", "r");
if (!lsofFile_p)
{
return -1;
}
char buffer[1024];
char *line_p = fgets(buffer, sizeof(buffer), lsofFile_p);
pclose(lsofFile_p);
编辑:将问题误读为想要将输出传递给另一个程序,而不是另一个函数。popen() 几乎可以肯定是你想要的。
系统使您可以完全访问外壳。如果你想继续使用它,你可以通过 system("ls > tempfile.txt") 将它的输出重定向到一个临时文件,但是选择一个安全的临时文件是很痛苦的。或者,您甚至可以通过另一个程序重定向它:system("ls | otherprogram");
有些人可能会推荐 popen() 命令。如果您可以自己处理输出,这就是您想要的:
FILE *output = popen("ls", "r");
这将为您提供一个 FILE 指针,您可以从中读取命令的输出。
您还可以使用 pipe() 调用创建连接,结合 fork() 创建新进程,dup2() 更改它们的标准输入和输出,exec() 运行新程序,wait()在主程序中等待他们。这只是像 shell 一样设置管道。有关详细信息和示例,请参见 pipe() 手册页。
函数popen()
等不会重定向 stderr 等;我为此写作popen3()
。
这是我的 popen3() 的一个保龄球版本:
int popen3(int fd[3],const char **const cmd) {
int i, e;
int p[3][2];
pid_t pid;
// set all the FDs to invalid
for(i=0; i<3; i++)
p[i][0] = p[i][1] = -1;
// create the pipes
for(int i=0; i<3; i++)
if(pipe(p[i]))
goto error;
// and fork
pid = fork();
if(-1 == pid)
goto error;
// in the parent?
if(pid) {
// parent
fd[STDIN_FILENO] = p[STDIN_FILENO][1];
close(p[STDIN_FILENO][0]);
fd[STDOUT_FILENO] = p[STDOUT_FILENO][0];
close(p[STDOUT_FILENO][1]);
fd[STDERR_FILENO] = p[STDERR_FILENO][0];
close(p[STDERR_FILENO][1]);
// success
return 0;
} else {
// child
dup2(p[STDIN_FILENO][0],STDIN_FILENO);
close(p[STDIN_FILENO][1]);
dup2(p[STDOUT_FILENO][1],STDOUT_FILENO);
close(p[STDOUT_FILENO][0]);
dup2(p[STDERR_FILENO][1],STDERR_FILENO);
close(p[STDERR_FILENO][0]);
// here we try and run it
execv(*cmd,const_cast<char*const*>(cmd));
// if we are there, then we failed to launch our program
perror("Could not launch");
fprintf(stderr," \"%s\"\n",*cmd);
_exit(EXIT_FAILURE);
}
// preserve original error
e = errno;
for(i=0; i<3; i++) {
close(p[i][0]);
close(p[i][1]);
}
errno = e;
return -1;
}
最有效的方法是stdout
直接使用文件描述符,绕过FILE
流:
pid_t popen2(const char *command, int * infp, int * outfp)
{
int p_stdin[2], p_stdout[2];
pid_t pid;
if (pipe(p_stdin) == -1)
return -1;
if (pipe(p_stdout) == -1) {
close(p_stdin[0]);
close(p_stdin[1]);
return -1;
}
pid = fork();
if (pid < 0) {
close(p_stdin[0]);
close(p_stdin[1]);
close(p_stdout[0]);
close(p_stdout[1]);
return pid;
} else if (pid == 0) {
close(p_stdin[1]);
dup2(p_stdin[0], 0);
close(p_stdout[0]);
dup2(p_stdout[1], 1);
dup2(::open("/dev/null", O_WRONLY), 2);
/// Close all other descriptors for the safety sake.
for (int i = 3; i < 4096; ++i) {
::close(i);
}
setsid();
execl("/bin/sh", "sh", "-c", command, NULL);
_exit(1);
}
close(p_stdin[0]);
close(p_stdout[1]);
if (infp == NULL) {
close(p_stdin[1]);
} else {
*infp = p_stdin[1];
}
if (outfp == NULL) {
close(p_stdout[0]);
} else {
*outfp = p_stdout[0];
}
return pid;
}
要读取子用途的输出,popen2()
如下所示:
int child_stdout = -1;
pid_t child_pid = popen2("ls", 0, &child_stdout);
if (!child_pid) {
handle_error();
}
char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));
写入和读取:
int child_stdin = -1;
int child_stdout = -1;
pid_t child_pid = popen2("grep 123", &child_stdin, &child_stdout);
if (!child_pid) {
handle_error();
}
const char text = "1\n2\n123\n3";
ssize_t bytes_written = write(child_stdin, text, sizeof(text) - 1);
char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));
在 Windows 中,不使用 system(),而是使用 CreateProcess,将输出重定向到管道并连接到管道。
我猜这也可能以某种 POSIX 方式实现?
功能popen()
和pclose()
可能是您正在寻找的。
以glibc 手册为例。
实际上,我刚刚检查过,并且:
popen 是有问题的,因为该过程是分叉的。因此,如果您需要等待 shell 命令执行,那么您就有可能错过它。就我而言,我的程序甚至在管道开始工作之前就关闭了。
我最终在 linux 上使用带有 tar 命令的系统调用。system的返回值是tar的结果。
所以:如果你需要返回值,那么不仅不需要使用popen,它可能不会做你想要的。
在此页面中:capture_the_output_of_a_child_process_in_c描述了使用 popen 与使用 fork/exec/dup2/STDOUT_FILENO 方法的局限性。
我猜这个限制可能是我的问题:
它返回一个 stdio 流,而不是原始文件描述符,后者不适合异步处理输出。
如果我有其他方法的解决方案,我会回到这个答案。
我不完全确定它在标准 C 中是否可行,因为两个不同的进程通常不共享内存空间。我能想到的最简单的方法是让第二个程序将其输出重定向到一个文本文件(程序名> textfile.txt),然后将该文本文件读回进行处理。但是,这可能不是最好的方法。