I am implementing a shell-like program in C++. It has a loop that reads from cin, forks, and waits for the child.
This works fine if the input is interactive or if it's piped from another program. However, when the input is a bash heredoc, the program rereads parts of the input (sometimes indefinitely).
I understand that the child process inherits the parent's file descriptors, including shared file offset. However, the child in this example does not read anything from cin, so I think it shouldn't touch the offset. I'm kind of stumped about why this is happening.
test.cpp:
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
std::string line;
while (std::getline(std::cin, line)) {
pid_t pid = fork();
if (pid == 0) { // child
break; // exit immediately
}
else if (pid > 0) { // parent
waitpid(pid, nullptr, 0);
}
else { // error
perror("fork");
}
std::cout << getpid() << ": " << line << "\n";
}
return 0;
}
I compile it as follows:
g++ test.cpp -std=c++11
Then I run it with:
./a.out <<EOF
hello world
goodbye world
EOF
Output:
7754: hello world
7754: goodbye world
7754: goodbye world
If I add a third line foo bar
to the input command, the program gets stuck in an infinite loop:
13080: hello world
13080: goodbye world
13080: foo bar
13080: o world
13080: goodbye world
13080: foo bar
13080: o world
[...]
Versions:
I was able to reproduce this problem, not only using the heredoc but also using a standard file redirection.
Here is the test script that I used. In both the first and second cases, I got a duplication of the second line of input.
./a.out < Input.txt
echo
cat Input.txt | ./a.out
echo
./a.out <<EOF
hello world
goodbye world
EOF
Closing the stdin
before exiting in the child seems to eliminate both of the problems.
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <sys/wait.h>
#include <limits>
int main(int argc, char **argv)
{
std::string line;
while (std::getline(std::cin, line)) {
pid_t pid = fork();
if (pid == 0) { // child
close(STDIN_FILENO);
break; // exit after first closing stdin
}
else if (pid > 0) { // parent
waitpid(pid, nullptr, 0);
}
else { // error
perror("fork");
}
std::cout << getpid() << ": " << line << "\n";
}
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With