模仿 unix shell 管道
Imitate unix shell pipe
教学任务:想模仿管道符号(命令,方法)“|”工作。程序像 unix shell 一样从 STDIN 获取命令:
command1 | command2 | command3 | ....
并且应该执行它,将 STDIN|STDOUT 重定向到每个命令的管道。最终输出重定向到 result.out 文件。应该只使用 execlp 和 fork。
第一个变体:适用于 1-2 个命令,但冻结 3 个或更多命令。我做错了什么:似乎我关闭了所有管道描述符?
现在在第二个变体 execute_line 中得到了简化,现在出现了另一个问题:输出混乱。如何在命令之间正确传递管道?
第三个变体:最接近正确,添加了更多调试信息。问题:如何正确连接中间children?
第 4 个变体,固定逻辑,几乎正确:使用 1 个、3 个或更多命令工作正常,使用 2 个命令开始失败(之前工作正确)- 奇怪 :)
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
void split(const string& str, vector<string> &tokens,
const string &delimiters = " ")
{
// Skip delimiters at beginning.
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first "non-delimiter".
string::size_type pos = str.find_first_of(delimiters, lastPos);
while (string::npos != pos || string::npos != lastPos)
{
// Found a token, add it to the vector.
tokens.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters. Note the "not_of"
lastPos = str.find_first_not_of(delimiters, pos);
// Find next "non-delimiter"
pos = str.find_first_of(delimiters, lastPos);
}
}
inline string trim(string &str)
{
const string whitespaces(" \t\f\v\n\r");
string::size_type pos = str.find_first_not_of(whitespaces);
if(pos != string::npos)
str.erase(0, pos); // prefixing spaces
pos = str.find_last_not_of(whitespaces);
if(pos != string::npos)
str.erase(pos + 1); // surfixing spaces
return str;
}
void parse_command(string &command, string &name, string &argc)
{
command = trim(command);
string::size_type pos = command.find_first_of(' ');
if(pos != string::npos) {
name = command.substr(0, pos);
argc = command.substr(pos + 1, command.length() - pos - 1);
} else {
name = command;
argc = "";
}
}
void exec_command(uint n, vector<string> &commands)
{
string name, args;
parse_command(commands[n], name, args);
if(args.length() > 0)
execlp(name.c_str(), name.c_str(), args.c_str(), NULL);
else
execlp(name.c_str(), name.c_str(), NULL);
}
// who ----(stdout)---> pfd[1] --- pfd[0] ----(stdin)---> wc -l
void execute_line(vector<string> &commands, uint i, int *parent_pfd = 0)
{
int pfd[2];
pipe(pfd);
if(i > 0 && !fork()) {
// Child
printf("Child, i: %d\n", i);
if(i > 1) {
execute_line(commands, i-1, pfd);
close(pfd[1]);
close(pfd[0]);
} else {
printf("Deeper child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i-1]).c_str(),
parent_pfd[0], parent_pfd[1], pfd[0], pfd[1]);
close(STDOUT_FILENO);
// if(parent_pfd)
// dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
// else
dup2(pfd[1], STDOUT_FILENO); // Copy STDOUT to pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i - 1, commands);
}
} else {
if(parent_pfd) {
printf("Middle Child, i: %d\n", i);
printf("Middle child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i]).c_str(), parent_pfd[0], parent_pfd[1],
pfd[0], pfd[1]);
close(STDIN_FILENO);
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(STDOUT_FILENO);
dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i, commands);
} else {
printf("Final, i: %d\n", i);
printf("Final %d: %s, pfd=%p, parent_pfd=%p, pfd[0]=%d, pfd[1]=file\n",
getpid(), trim(commands[i]).c_str(), pfd, parent_pfd, pfd[0]);
int fd = open("result.out", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
dup2(fd, STDOUT_FILENO); // Copy stdout to file
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(pfd[0]); // Close as was redirected
close(pfd[1]); // Close WRITE as not necessary here
close(fd);
exec_command(i, commands);
}
}
}
int main()
{
char buffer[1024];
ssize_t size = read(STDIN_FILENO, buffer, 1024);
if(size > 0) {
buffer[size] = '[=12=]';
string command = buffer;
vector<string> commands;
split(command, commands, "|");
execute_line(commands, commands.size() - 1);
}
return 0;
}
您用来将管道连接到标准输入和输出的逻辑看起来有问题。
int pfd[2];
pipe(pfd);
您首先创建一个管道,大概是为了将一个进程的标准输出连接到另一个进程的标准输入。没关系。
现在,让我们看一下将要执行其中一个进程的代码部分之一:
close(STDIN_FILENO);
close(STDOUT_FILENO);
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
dup2(pfd[1], STDOUT_FILENO); // Copy STDOUT to pipe out
close(pfd[0]); // Close as was redirected
close(pfd[1]); // Close as was redirected
exec_command(i, commands);
现在,我什至不需要解释这个。您可以在这里阅读自己的评论,然后尝试解释为什么将同一管道的两端连接到同一进程的标准输入和输出?这是没有意义的。管道应该将一个进程的标准输入附加到另一个进程的标准输出。在这种情况下,执行进程并将其标准输入附加到标准输出是没有意义的。这让我很头疼。
这是这里的问题之一,但这里可能还有其他一些问题,仔细观察后会很明显。
这里的整体方法对我来说似乎太复杂了。这个设置管道的递归函数实际上应该只有一个决策点:这是管道中的最后一个命令吗?如果是这样,做一件事。如果没有,则执行其他涉及递归的操作,以设置管道的其余部分。
在我看来这里有三四个决策点,所以这里的整体逻辑虽然稍微有点复杂,但没有错,应该简化一下。正如您的评论所述,您不必为管道的 "middle" 部分进行任何特殊编码。您是否正在处理管道中的最后一个命令。而已。尝试以这种方式重写您的函数。它应该更简单,并且效果更好。
我正在使用下一个逻辑(我的解决方案的伪代码):
我先把所有的命令通过分配各自的描述符放在一个链表中,然后依次执行。
int pfd_cur[2];
int in = -1;
int out = -1;
while (number_of_commands) { // process all commands from first to last
number_of_commands--; // and set cur_command_number
if(cur_command_number == 0) {
if(number_of_commands > 0) {
pipe(pfd_cur);
out = pfd_cur[1];
in = pfd_cur[0];
// We process first command
// collect command (STDIN_FILENO, out);
} else {
// We process first command and we have only one command
// collect command (STDIN_FILENO, STDOUT_FILENO);
}
} else {
if(number_of_commands == 0)
{
// We process last command
// collect command(in, STDOUT_FILENO);
} else {
pipe(pfd_cur);
out = pfd_cur[1];
// We process intermediate command
// collect command (in, out);
in = pfd_cur[0];
}
}
}
我在链表中存储连接过程数据的结构
struct node {
// pipe num
int in_fd;
int out_fd;
const char *command;
char **argv;
int agrc;
struct node *next;
struct node *prev;
};
我执行每个存储节点的函数
void execute(struct node* command)
{
if(!fork())
{
if(command->out_fd != 1)
{
close(STDOUT_FILENO);
dup2(command->out_fd, STDOUT_FILENO);
}
if(command->in_fd != 0)
{
close(STDIN_FILENO);
dup2(command->in_fd, STDIN_FILENO);
}
execvp(command->command, command->argv);
close(command->out_fd);
close(command->in_fd);
}
}
在最终变体中只是简单的 if 表达式(应该是 i >= 1
)错误。所以正确的 execute_line() 方法变体是:
void execute_line(vector<string> &commands, size_t i, int *parent_pfd = 0)
{
int pfd[2];
pipe(pfd);
if(i > 0 && !fork()) {
// Child
if(i >= 1) {
execute_line(commands, i-1, pfd);
close(pfd[1]);
close(pfd[0]);
} else {
printf("Deeper child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i-1]).c_str(),
parent_pfd[0], parent_pfd[1], pfd[0], pfd[1]);
close(STDOUT_FILENO);
dup2(pfd[1], STDOUT_FILENO); // Copy STDOUT to pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i - 1, commands);
}
} else {
if(parent_pfd) {
printf("Middle child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i]).c_str(), parent_pfd[0], parent_pfd[1],
pfd[0], pfd[1]);
close(STDIN_FILENO);
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(STDOUT_FILENO);
dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i, commands);
} else {
printf("Final %d: %s, pfd=%p, parent_pfd=%p, pfd[0]=%d, pfd[1]=file\n",
getpid(), trim(commands[i]).c_str(), pfd, parent_pfd, pfd[0]);
int fd = open("result.out", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
dup2(fd, STDOUT_FILENO); // Copy stdout to file
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(pfd[0]); // Close as was redirected
close(pfd[1]); // Close WRITE as not necessary here
close(fd);
exec_command(i, commands);
}
}
}
教学任务:想模仿管道符号(命令,方法)“|”工作。程序像 unix shell 一样从 STDIN 获取命令:
command1 | command2 | command3 | ....
并且应该执行它,将 STDIN|STDOUT 重定向到每个命令的管道。最终输出重定向到 result.out 文件。应该只使用 execlp 和 fork。
第一个变体:适用于 1-2 个命令,但冻结 3 个或更多命令。我做错了什么:似乎我关闭了所有管道描述符?
现在在第二个变体 execute_line 中得到了简化,现在出现了另一个问题:输出混乱。如何在命令之间正确传递管道?
第三个变体:最接近正确,添加了更多调试信息。问题:如何正确连接中间children?
第 4 个变体,固定逻辑,几乎正确:使用 1 个、3 个或更多命令工作正常,使用 2 个命令开始失败(之前工作正确)- 奇怪 :)
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
void split(const string& str, vector<string> &tokens,
const string &delimiters = " ")
{
// Skip delimiters at beginning.
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first "non-delimiter".
string::size_type pos = str.find_first_of(delimiters, lastPos);
while (string::npos != pos || string::npos != lastPos)
{
// Found a token, add it to the vector.
tokens.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters. Note the "not_of"
lastPos = str.find_first_not_of(delimiters, pos);
// Find next "non-delimiter"
pos = str.find_first_of(delimiters, lastPos);
}
}
inline string trim(string &str)
{
const string whitespaces(" \t\f\v\n\r");
string::size_type pos = str.find_first_not_of(whitespaces);
if(pos != string::npos)
str.erase(0, pos); // prefixing spaces
pos = str.find_last_not_of(whitespaces);
if(pos != string::npos)
str.erase(pos + 1); // surfixing spaces
return str;
}
void parse_command(string &command, string &name, string &argc)
{
command = trim(command);
string::size_type pos = command.find_first_of(' ');
if(pos != string::npos) {
name = command.substr(0, pos);
argc = command.substr(pos + 1, command.length() - pos - 1);
} else {
name = command;
argc = "";
}
}
void exec_command(uint n, vector<string> &commands)
{
string name, args;
parse_command(commands[n], name, args);
if(args.length() > 0)
execlp(name.c_str(), name.c_str(), args.c_str(), NULL);
else
execlp(name.c_str(), name.c_str(), NULL);
}
// who ----(stdout)---> pfd[1] --- pfd[0] ----(stdin)---> wc -l
void execute_line(vector<string> &commands, uint i, int *parent_pfd = 0)
{
int pfd[2];
pipe(pfd);
if(i > 0 && !fork()) {
// Child
printf("Child, i: %d\n", i);
if(i > 1) {
execute_line(commands, i-1, pfd);
close(pfd[1]);
close(pfd[0]);
} else {
printf("Deeper child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i-1]).c_str(),
parent_pfd[0], parent_pfd[1], pfd[0], pfd[1]);
close(STDOUT_FILENO);
// if(parent_pfd)
// dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
// else
dup2(pfd[1], STDOUT_FILENO); // Copy STDOUT to pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i - 1, commands);
}
} else {
if(parent_pfd) {
printf("Middle Child, i: %d\n", i);
printf("Middle child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i]).c_str(), parent_pfd[0], parent_pfd[1],
pfd[0], pfd[1]);
close(STDIN_FILENO);
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(STDOUT_FILENO);
dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i, commands);
} else {
printf("Final, i: %d\n", i);
printf("Final %d: %s, pfd=%p, parent_pfd=%p, pfd[0]=%d, pfd[1]=file\n",
getpid(), trim(commands[i]).c_str(), pfd, parent_pfd, pfd[0]);
int fd = open("result.out", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
dup2(fd, STDOUT_FILENO); // Copy stdout to file
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(pfd[0]); // Close as was redirected
close(pfd[1]); // Close WRITE as not necessary here
close(fd);
exec_command(i, commands);
}
}
}
int main()
{
char buffer[1024];
ssize_t size = read(STDIN_FILENO, buffer, 1024);
if(size > 0) {
buffer[size] = '[=12=]';
string command = buffer;
vector<string> commands;
split(command, commands, "|");
execute_line(commands, commands.size() - 1);
}
return 0;
}
您用来将管道连接到标准输入和输出的逻辑看起来有问题。
int pfd[2];
pipe(pfd);
您首先创建一个管道,大概是为了将一个进程的标准输出连接到另一个进程的标准输入。没关系。
现在,让我们看一下将要执行其中一个进程的代码部分之一:
close(STDIN_FILENO);
close(STDOUT_FILENO);
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
dup2(pfd[1], STDOUT_FILENO); // Copy STDOUT to pipe out
close(pfd[0]); // Close as was redirected
close(pfd[1]); // Close as was redirected
exec_command(i, commands);
现在,我什至不需要解释这个。您可以在这里阅读自己的评论,然后尝试解释为什么将同一管道的两端连接到同一进程的标准输入和输出?这是没有意义的。管道应该将一个进程的标准输入附加到另一个进程的标准输出。在这种情况下,执行进程并将其标准输入附加到标准输出是没有意义的。这让我很头疼。
这是这里的问题之一,但这里可能还有其他一些问题,仔细观察后会很明显。
这里的整体方法对我来说似乎太复杂了。这个设置管道的递归函数实际上应该只有一个决策点:这是管道中的最后一个命令吗?如果是这样,做一件事。如果没有,则执行其他涉及递归的操作,以设置管道的其余部分。
在我看来这里有三四个决策点,所以这里的整体逻辑虽然稍微有点复杂,但没有错,应该简化一下。正如您的评论所述,您不必为管道的 "middle" 部分进行任何特殊编码。您是否正在处理管道中的最后一个命令。而已。尝试以这种方式重写您的函数。它应该更简单,并且效果更好。
我正在使用下一个逻辑(我的解决方案的伪代码):
我先把所有的命令通过分配各自的描述符放在一个链表中,然后依次执行。
int pfd_cur[2];
int in = -1;
int out = -1;
while (number_of_commands) { // process all commands from first to last
number_of_commands--; // and set cur_command_number
if(cur_command_number == 0) {
if(number_of_commands > 0) {
pipe(pfd_cur);
out = pfd_cur[1];
in = pfd_cur[0];
// We process first command
// collect command (STDIN_FILENO, out);
} else {
// We process first command and we have only one command
// collect command (STDIN_FILENO, STDOUT_FILENO);
}
} else {
if(number_of_commands == 0)
{
// We process last command
// collect command(in, STDOUT_FILENO);
} else {
pipe(pfd_cur);
out = pfd_cur[1];
// We process intermediate command
// collect command (in, out);
in = pfd_cur[0];
}
}
}
我在链表中存储连接过程数据的结构
struct node {
// pipe num
int in_fd;
int out_fd;
const char *command;
char **argv;
int agrc;
struct node *next;
struct node *prev;
};
我执行每个存储节点的函数
void execute(struct node* command)
{
if(!fork())
{
if(command->out_fd != 1)
{
close(STDOUT_FILENO);
dup2(command->out_fd, STDOUT_FILENO);
}
if(command->in_fd != 0)
{
close(STDIN_FILENO);
dup2(command->in_fd, STDIN_FILENO);
}
execvp(command->command, command->argv);
close(command->out_fd);
close(command->in_fd);
}
}
在最终变体中只是简单的 if 表达式(应该是 i >= 1
)错误。所以正确的 execute_line() 方法变体是:
void execute_line(vector<string> &commands, size_t i, int *parent_pfd = 0)
{
int pfd[2];
pipe(pfd);
if(i > 0 && !fork()) {
// Child
if(i >= 1) {
execute_line(commands, i-1, pfd);
close(pfd[1]);
close(pfd[0]);
} else {
printf("Deeper child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i-1]).c_str(),
parent_pfd[0], parent_pfd[1], pfd[0], pfd[1]);
close(STDOUT_FILENO);
dup2(pfd[1], STDOUT_FILENO); // Copy STDOUT to pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i - 1, commands);
}
} else {
if(parent_pfd) {
printf("Middle child %d: %s, parent_pfd[0]=%d, parent_pfd[1]=%d, "
"pfd[0]=%d, pfd[1]=%d\n",
getpid(), trim(commands[i]).c_str(), parent_pfd[0], parent_pfd[1],
pfd[0], pfd[1]);
close(STDIN_FILENO);
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(STDOUT_FILENO);
dup2(parent_pfd[1], STDOUT_FILENO); // Copy STDOUT to parent pipe out
close(pfd[1]);
close(pfd[0]);
exec_command(i, commands);
} else {
printf("Final %d: %s, pfd=%p, parent_pfd=%p, pfd[0]=%d, pfd[1]=file\n",
getpid(), trim(commands[i]).c_str(), pfd, parent_pfd, pfd[0]);
int fd = open("result.out", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
dup2(fd, STDOUT_FILENO); // Copy stdout to file
dup2(pfd[0], STDIN_FILENO); // Copy STDIN to pipe in
close(pfd[0]); // Close as was redirected
close(pfd[1]); // Close WRITE as not necessary here
close(fd);
exec_command(i, commands);
}
}
}