在C++中,当cin是bash heredoc时调用fork会导致重复的输入片段

Kev*_*hen 11 c++ bash fork heredoc istream

我正在用C++实现类似shell的程序.它有一个从cin,forks读取并等待孩子的循环.

如果输入是交互式的,或者如果它是从另一个程序传送的,那么这种方法很好.但是,当输入是bash heredoc时,程序会重新读取部分输入(有时是无限期).

我知道子进程继承了父进程的文件描述符,包括共享文件偏移量.但是,这个例子中的孩子没有从cin读取任何东西,所以我认为它不应该触及偏移量.我有点难过为什么会这样.


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;
}
Run Code Online (Sandbox Code Playgroud)

我编译如下:

g++ test.cpp -std=c++11
Run Code Online (Sandbox Code Playgroud)

然后我运行它:

./a.out <<EOF
hello world
goodbye world
EOF
Run Code Online (Sandbox Code Playgroud)

输出:

7754: hello world
7754: goodbye world
7754: goodbye world
Run Code Online (Sandbox Code Playgroud)

如果我foo bar在输入命令中添加第三行,程序将陷入无限循环:

13080: hello world
13080: goodbye world
13080: foo bar
13080: o world
13080: goodbye world
13080: foo bar
13080: o world
[...]
Run Code Online (Sandbox Code Playgroud)

版本:

  • Linux内核:4.4.0-51-通用
  • Ubuntu:16.04.1 LTS(xenial)
  • bash:GNU bash,版本4.3.46(1)-release(x86_64-pc-linux-gnu)
  • gcc:g ++(Ubuntu 5.4.0-6ubuntu1~16.04.4)5.4.0 20160609

mer*_*011 2

我能够重现这个问题,不仅使用heredoc,还使用标准文件重定向。

这是我使用的测试脚本。在第一种和第二种情况下,我得到了第二行输入的重复。

./a.out < Input.txt
echo

cat Input.txt | ./a.out
echo

./a.out <<EOF
hello world
goodbye world
EOF
Run Code Online (Sandbox Code Playgroud)

关闭stdin孩子的退出似乎可以消除这两个问题。

#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;
}
Run Code Online (Sandbox Code Playgroud)