В С++ вызов fork, когда cin является bash heredoc вызывает повторяющиеся входные фрагменты

Я использую 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;
}

Я скомпилирую его следующим образом:

g++ test.cpp -std=c++11

Затем я запускаю его с помощью:

./a.out <<EOF
hello world
goodbye world
EOF

Вывод:

7754: hello world
7754: goodbye world
7754: goodbye world

Если я добавлю третью строку foo bar во входную команду, программа застрянет в бесконечном цикле:

13080: hello world
13080: goodbye world
13080: foo bar
13080: o world
13080: goodbye world
13080: foo bar
13080: o world
[...]

Версии:

  • Ядро Linux: 4.4.0-51-generic
  • 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

Ответ 1

Я смог воспроизвести эту проблему, не только используя heredoc, но также используя стандартное перенаправление файлов.

Вот тест script, который я использовал. Как в первом, так и во втором случаях я получил дублирование второй строки ввода.

./a.out < Input.txt
echo

cat Input.txt | ./a.out
echo

./a.out <<EOF
hello world
goodbye world
EOF

Закрытие 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;
}