在 Linux 中调用外部程序并重定向输入输出。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
// 该函数调用 cmd 程序,并传入 argv 参数列表,使用 envp 环境变量
// 如果 fin >= 0,则会通过 fin 返回一个文件描述符,是 cmd 重定向后的标准输入
// 如果 fout >= 0,则会通过 fout 返回一个文件描述符,是 cmd 重定向后的标准输出
// 如果 ferr >= 0,则会通过 ferr 返回一个文件描述符,是 cmd 重定向后的标准错误
// background 表示 cmd 是否在后台运行,不等待其运行结束
// argv 和 envp 是字符串数组,字符串以 \0 结尾,数组以空字符串结尾
// argv 第一个元素必须是要执行的程序本身,第二个元素开始才是要传入的参数
// environ 是当前进程的环境变量,可以通过 getenv、putenv、setenv、unsetenv 读写
int run_ex(const char *cmd, char *const *argv, int &fin, int &fout, int &ferr,
bool background = false, char **envp = environ)
{
// 初始化为 -1,因为要用其值判断管道是否创建成功
int fdin [2] = {-1, -1};
int fdout[2] = {-1, -1};
int fderr[2] = {-1, -1};
try
{
// 如果 fin >= 0,则创建 fdin 管道,并将管道的一端通过 fin 返回
if (fin >= 0) {
if (pipe(fdin) == 0)
fin = fdin[1];
else {
perror("pipe(fdin)");
throw(-1);
}
}
// 如果 fout >= 0,则创建 fdout 管道,并将管道的一端通过 fout 返回
if (fout >= 0) {
if (pipe(fdout) == 0)
fout = fdout[0];
else {
perror("pipe(fdout)");
throw(-1);
}
}
// 如果 ferr >= 0,则创建 fderr 管道,并将管道的一端通过 ferr 返回
if (ferr >= 0) {
if (pipe(fderr) == 0)
ferr = fderr[0];
else {
perror("pipe(fderr)");
throw(-1);
}
}
// 创建子进程
pid_t pid = fork();
// 子进程创建失败
if (pid < 0)
throw(-1);
if (pid == 0) {
// 子进程要执行的代码
if (fin >= 0) close(fin);
if (fout >= 0) close(fout);
if (ferr >= 0) close(ferr);
// 将 fdin[0] 文件描述符复制给 stdin,然后关闭 fdin[0]
if (fin >= 0 && fdin[0] != STDIN_FILENO) {
if (dup2(fdin[0], STDIN_FILENO) != STDIN_FILENO) {
perror("dup2(fdin[0], stdin)");
_Exit(-1);
} else
close(fdin[0]);
}
// 将 fdout[1] 文件描述符复制给 stdout,然后关闭 fdout[1]
if (fout >= 0 && fdout[1] != STDOUT_FILENO) {
if (dup2(fdout[1], STDOUT_FILENO) != STDOUT_FILENO) {
perror("dup2(fdout[1], stdout)");
_Exit(-1);
} else
close(fdout[1]);
}
// 将 fderr[1] 文件描述符复制给 stderr,然后关闭 fderr[1]
if (ferr >= 0 && fderr[1] != STDERR_FILENO) {
if (dup2(fderr[1], STDERR_FILENO) != STDERR_FILENO) {
perror("dup2(fderr[1], stderr)");
_Exit(-1);
} else
close(fderr[1]);
}
// 子进程要执行的代码
// Exec 函数族命名规律:
// l (list) 使用命令行参数列表
// v (vector) 使用命令行参数数组
// p (path) 从 PATH 中搜索程序
// e (environment) 传入自定义环境变量
execvpe(cmd, argv, envp);
// 如果命令启动成功,则不会到达这里,到达这里表示命令启动失败,
// 比如命令不存在,参数格式错误等
// 此时不能用 exit 返回,要直接调用 _Exit 中止程序,否则子进程
// 会退回到 main 函数继续执行
_Exit(-1);
}
else if (pid > 0) {
// 父进程要执行的代码
if (fin >= 0) { close(fdin [0]); fdin [1] = -1; }
if (fout >= 0) { close(fdout[1]); fdout[0] = -1; }
if (ferr >= 0) { close(fderr[1]); fderr[0] = -1; }
if (background)
// 如果不等待,则直接返回子进程的 pid
return pid;
else {
// 等待任意一个子进程结束
// 正常情况下,如果不等待子进程结束,则当父进程结束后,子进程会继续运行。
// 调试模式下父进程执行完毕,可能会杀死子进程。
wait(&pid);
// 如果子进程成功退出
if (WIFEXITED(pid))
// 返回退出码
return WEXITSTATUS(pid);
else
// 否则返回 -1
return -1;
}
}
}
catch (int n)
{
// 这些异常都是在创建子进程之前出现的
if (fdin [0] >= 0) { close(fdin [0]); }
if (fdout[1] >= 0) { close(fdout[1]); }
if (fderr[1] >= 0) { close(fderr[1]); }
if (fin >= 0) { close(fin ); fin = -1; }
if (fout >= 0) { close(fout); fout = -1; }
if (ferr >= 0) { close(ferr); ferr = -1; }
return n;
}
return -1;
}
int run(string cmd, char *const *argv)
{
int fin = -1, fout = -1, ferr = -1;
return run_ex(cmd.c_str(), argv, fin, fout, ferr);
}
int run_in(string cmd, char *const *argv, string sin, char **envp = environ)
{
int fin = 0, fout = -1, ferr = -1;
int ret = run_ex(cmd.c_str(), argv, fin, fout, ferr, true, envp);
if (ret >= 0)
write(fin, sin.c_str(), sin.length());
close(fin);
return ret;
}
char *run_out(string cmd, char *const *argv, char **envp = environ)
{
int fin = -1, fout = 0, ferr = -1;
int ret = run_ex(cmd.c_str(), argv, fin, fout, ferr, false, envp);
char *s;
if (ret >= 0) {
const int BUFSIZE = 1024;
int len = BUFSIZE;
s = (char *)malloc(len);
int n = read(fout, s, BUFSIZE);
while (n == BUFSIZE) {
s = (char *)realloc(s, len + BUFSIZE);
n = read(fout, s + len, BUFSIZE);
len += n;
};
s = (char *)realloc(s, len + 1);
s[len] = 0;
}
close(fout);
return s;
}
int main(void)
{
char *const args1[4] = {(char *)"echo", (char *)"Hello world", (char *)"!", NULL};
run("echo", args1);
char *const args2[2] = {(char *)"cat", NULL};
run_in("cat", args2, "Hello world!\n", NULL);
char *const args3[2] = {(char *)"ls", NULL};
char *s = run_out("ls", args3, NULL);
printf("%s", s);
free(s);
return 0;
}