i am working in a project to implement a mini linux shell, i want to implement a pipe command where it basically works like this: command1 | command2
command1 | command2
: using the pipe character “ |
” will produce a pipe, redirects command1 stdout to its write channel and command2 stdin to its read channel.
or:
command1 |& command2
: using the pipe character “ |&
” will produce a pipe, redirects command1 stderr to the pipe's write channel and command2 stdin to the pipe's read channel.
now command 1 can be either an external command from linux that i run using execv or a built in command that i wrote, and command2 is always an external command
my code is not working correctly and i don't know where is the problem exactly, because i implemented many commands and they all worked perfect for example (cp, redirection... ), so the base is good in my code, but the pipe is just wrong: for example if the command is : showpid |./parser.exe 1
where parser.exe is a giving file that does parsing on the command, for example here if showpid prints: shell process pid is 12311
, then calling this command showpid |./parser.exe 1
the output should be "shell"
, but in my code the output is shell process pid is 12311
this is my pipe command implementation:
this is the class of the pipe command:
class PipeCommand : public Command {
private:
int pipeNum;
int split;
string cmd1;
string cmd2;
public:
PipeCommand(const char* cmd_line);
virtual ~PipeCommand() {}
void execute() override;
};
// the pipe constructor , here i want to extract each command from the right and left side of the pipe from the cmd_line , which is the command line that i get
// fro example : " showpid | grep 1 "
PipeCommand::PipeCommand(const char* cmd_line):Command(cmd_line) {
pipeNum = -1;
isBackground = _isBackgroundComamnd(cmd_line);
string cmd1 = "", cmd2 = "";
int split = -1;
for (int i = 0; i < this->num_args; i++) {
if (strcmp(args[i], "|") == 0) {
split = i;
pipeNum = 1;
break;
}
if (strcmp(args[i], "|&") == 0) {
split = i;
pipeNum = 2;
break;
}
}
for (int i = 0; i < split; i++) {
cmd1 = cmd1 + args[i] + " ";
}
for (int i = split + 1; i < num_args; i++) {
cmd2 = cmd2 + args[i] + " ";
}
// the implementation of the pipe command
void PipeCommand::execute() {
int pipeFd[2];
int pid;
pipe(pipeFd);
pid = fork();
if (pid == 0) { // child process .
close(pipeFd[1]);
dup2(pipeFd[1], pipeNum);
if (isBuiltInCMD(args[0])) { // if the command is built in which means i wrote it i run it like this ( this works fine i checked it)
Command *newCmd = CreateBuiltInCommand(const_cast<char *>(cmd1.c_str()));
newCmd->execute();
exit(0);
} else { // if the command is external than use execv
const char **argv = new const char *[4];
argv[0] = "/bin/bash";
argv[1] = "-c";
argv[2] = cmd1.c_str();
argv[3] = nullptr;
execv(argv[0], const_cast<char **>(argv));
perror("execvp failed");
}
} else { // the parent process , basically runs the command2 , which it can be only an external command
pid = fork(); // we fork again in the parent process
if (pid == 0) { // the child process executes the secomd command using execv
dup2(pipeFd[0], STDIN_FILENO);
close(pipeFd[0]);
dup2(pipeFd[0], pipeNum);
// execute
const char **argv = new const char *[4];
argv[0] = "/bin/bash";
argv[1] = "-c";
argv[2] = cmd2.c_str();
argv[3] = nullptr;
execv(argv[0], const_cast<char **>(argv));
perror("execvp failed");
} else { // the parent process waits
waitpid(pid,NULL,0);
close(pipeFd[1]);
close(pipeFd[0]);
}
}
}
I think you should look at the order that you are closing / duping file descriptors. Specifically:
The first command needs to use existing stdin (fd 0). Don't close it. But you should close existing stdout (fd 1) and THEN do the fd dup so it becomes 1.
The second command does it the other way.
I would test with a MUCH simpler example. Get the piping thing to work and THEN do the exec thing.
In a C/C++ world, you have 3 standard files when the program starts:
FD 0 is stdin -- Used for input FD 1 is stdout -- Used for normal output FD 2 is stderr -- Used for error output
When you do this:
grep foo < file.txt | grep bar
What the shell does is:
-Does the pipe call to get the input and output files -On the first grep for foo, close fd 0 (stdin) and open file.txt for input. It will land on 0, and thus is stdin to the grep command. -Close stdout and assign it to the out part of the pipe
On the second grep:
-Close 1 (stdin) -And move the pipe input portion to 1 so stdin is set.
Thus, in the end:
part 1 fd 0 (stdin) is the file part 1 fd 1 (stdout) is the output portion of the pipe part 2 fd 0 (stdin) is the input portion of the pipe
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.