I want to implement pipe command in shell, such as ls | cat | wc. Here's my pipe command implementation.
I use a pipe to store the results generated by every command. For every child process, it:
- obtain input from the pipe (generated by the last command)
- redirect stdout to pipe
- call
execvpto execute the command with the input
Here's my code:
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 << "$ ";
}
When I test the program by entering ls | cat. The last command successfully put the result of ls into the pipe. But I could not redirect input from pipe to STDIN_FILENO when executing the cat command.
The program just blocks when executing ls | cat. After debugging, I found that it's because the cat command is still waiting for user to input something, and the parent process is waiting for the child process to terminate.
I'm the one who raised this question. Now I've worked out the problem now and successfully implemented the multi-pipe program. I made 3 changes to the previous code:
pipe -> STDIN -> execvp -> STDOUT -> pipe. But if I only use 1 pipe, theSTDINandSTDOUTare connected. So In the current version of my program, I create a new pipe beforefork()and close its write end, but I record its read end in a variablefd_in.fd_inis set to 0 at first. Because some commands such ascatneed to obtain input from theSTDINinstead of pipe. After each command execution, thefd_inis updated to the current pipe's read end for the next command execution. While working with this problem, I found that pipe is not the same as local variable, which is pushed on the stack on process memory. It's stored in kernel memory rather than the process memory, so the created pipe won't go away immediately after the loop exits. But if we declares a local variable in a loop, it's gone after the loop exits. If we record the read end of the pipe, we could still reach it in the next loop.countand compare it to the length of command vector in order to determine if it is the last command to execute. If it is not the last command, write the result to the pipe. Otherwise, just print out the result to the console. Therefore, there's no need to use the system callreadto get the result out of pipe and store it in a buffer in the parent process. It's mainly for those command which print out results to the console immediately, such ascat. Besieds, it simplify the design.The new version of code as follows:
Thanks for Some programmmer dude's help. And I refer to an answer from this question in the process of solving this question. I hope my implementation will give you some inspirations.