我想在shell中实现管道命令,例如
ls | cat | wc
。这是我的管道命令实现。
我使用管道来存储每个命令生成的结果。对于每个子进程,它:
execvp
来执行输入的命令这是我的代码:
while(getline(std::cin, s)) {
if(s == "exit") {
return EXIT_SUCCESS;
}
// initialize a pipe
int fd[2];
if(pipe(fd) == -1) {
perror("pipe creation failed!");
return EXIT_FAILURE;
}
// split the command into multiple parts
vector<string> tokens;
boost::algorithm::split(tokens, s, boost::is_any_of("|"),boost::token_compress_on);
for(auto& command: tokens) {
// prepare to run the current command
// get the current command
boost::algorithm::trim(command);
// split the command into an array of args
vector<string> args;
boost::algorithm::split(args,command,boost::is_any_of(" "),boost::token_compress_on);
int argc = args.size();
if(argc < 1) {
cerr << "We need a command!" << endl;
return EXIT_FAILURE;
}
// run the current command
pid_t child = fork();
if(child == 0) {
// setup the file name and input arguments
const char* filename = args[0].c_str();
char** argv = new char*[argc + 1];
for(int i = 0; i < argc; i++) {
string args_str = args[i];
argv[i] = new char[10];
strcpy(argv[i],args_str.c_str());
}
argv[argc] = nullptr;
// write the pipe value into stdin
dup2(fd[0], STDIN_FILENO);
close(fd[0]);
// write stdout to the pipe
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
// use execvp() to run the commmand
execvp(filename,argv);
// exec didn't work, so an error must have been occurred
cerr << strerror(errno) << endl;
delete[] argv;
return EXIT_FAILURE;
}
// wait for the child process to complete
int status;
waitpid(child,&status,0);
}
// read out the pipe
char* buffer = new char[BUF_SIZ];
int count = read(fd[0],buffer,BUF_SIZ);
if(count > 0) {
fprintf(stdout, "%s", buffer);
}
delete buffer;
close(fd[0]);
close(fd[1]);
cout << "$ ";
}
当我通过输入
ls | cat
来测试程序时。最后一个命令成功地将 ls
的结果放入管道中。但在执行 cat
命令时,我无法将输入从管道重定向到 STDIN_FILENO。
程序在执行时只是阻塞
ls | cat
。经过调试,我发现这是因为cat
命令还在等待用户输入一些东西,而父进程正在等待子进程终止。
我是提出这个问题的人。现在我已经解决了这个问题并成功实现了多管道程序。我对之前的代码做了 3 处更改:
pipe -> STDIN -> execvp -> STDOUT -> pipe
。但如果我只使用 1 个管道,则 STDIN
和 STDOUT
是连接的。因此,在我的程序的当前版本中,我在 fork()
之前创建了一个新管道并关闭其写入端,但我将其读取端记录在变量 fd_in
中。fd_in
首先设置为0。因为有些命令如cat
需要从STDIN
而不是管道获取输入。每次命令执行后,fd_in
都会更新为当前管道的读取端,以便执行下一个命令。在解决这个问题时,我发现管道与局部变量不同,局部变量被推送到进程内存的堆栈上。它存储在内核内存中而不是进程内存中,因此创建的管道不会在循环退出后立即消失。但是如果我们在循环中声明一个局部变量,那么它在循环退出后就会消失。如果我们记录管道的读取端,我们仍然可以在下一个循环中到达它。count
并将其与命令向量的长度进行比较,以确定它是否是最后执行的命令。如果不是最后一个命令,则将结果写入管道。否则,只需将结果打印到控制台即可。因此,不需要使用系统调用read
从管道中获取结果并将其存储在父进程的缓冲区中。主要针对那些立即将结果打印到控制台的命令,例如cat
。除此之外,它简化了设计。新版本代码如下:
while (getline(std::cin, s))
{
if (s == "exit")
{
return EXIT_SUCCESS;
}
int fd[2];
int in_fd = 0; // input fd
// split the command into multiple parts
vector<string> tokens;
boost::algorithm::split(tokens, s, boost::is_any_of("|"), boost::token_compress_on);
int count = 1;
int command_num = tokens.size();
for (auto &command : tokens)
{
// initialize a pipe
if (pipe(fd) == -1)
{
perror("pipe creation failed!");
return EXIT_FAILURE;
}
// prepare to run the current command
// get the current command
boost::algorithm::trim(command);
// split the command into an array of args
vector<string> args;
boost::algorithm::split(args, command, boost::is_any_of(" "), boost::token_compress_on);
int argc = args.size();
if (argc < 1)
{
cerr << "We need a command!" << endl;
return EXIT_FAILURE;
}
// run the current command
pid_t child = fork();
if (child == 0)
{
// setup the file name and input arguments
const char *filename = args[0].c_str();
char **argv = new char *[argc + 1];
for (int i = 0; i < argc; i++)
{
string args_str = args[i];
argv[i] = new char[10];
strcpy(argv[i], args_str.c_str());
}
argv[argc] = nullptr;
if (in_fd != 0)
{
// write the pipe value into stdin
dup2(in_fd, STDIN_FILENO);
close(in_fd);
}
if (count != command_num)
{
// write stdout to the pipe
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
}
// use execvp() to run the commmand
execvp(filename, argv);
// exec didn't work, so an error must have been occurred
cerr << strerror(errno) << endl;
delete[] argv;
return EXIT_FAILURE;
}
// wait for the child process to complete
int status;
waitpid(child, &status, 0);
// close the current pipe write fd
close(fd[1]);
in_fd = fd[0];
count += 1;
}
// // read out the pipe
// char buffer[BUF_SIZ];
// int count = read(in_fd, buffer, BUF_SIZ);
// buffer[count] = '\0';
// if (count > 0)
// {
// fprintf(stdout, "%s", buffer);
// }
close(in_fd);
cout << "$ ";
}