Канал кодирования » |» в C : почему пустая строка вводит getline после конвейера?

#c #pipe #fork #getline #dup2

#c #канал #fork #getline #dup2

Вопрос:

Я кодирую макет оболочки и в настоящее время работаю над кодированием каналов с помощью dup2. Вот мой код:

 bool Pipe::execute() {
    int fds[2]; //will hold file descriptors
    pipe(fds);
    int status;
    int errorno;
    pid_t child;
    child = fork();

    if (-1 == child) {
        perror("fork failed");
    }
    if (child == 0) {
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        close(fds[1]);
        this->component->lchild->execute();
        _exit(1);
    }
    else if (child > 0) {
        dup2(fds[0], STDIN_FILENO);
        close(fds[0]);
        close(fds[1]);
        this->component->rchild->execute();
        waitpid(child, amp;status, 0);
        if ( WIFEXITED(status) ) {
            //printf("child exited with = %dn",WEXITSTATUS(status));
            if ( WEXITSTATUS(status) == 0) {
                cout << "pipe parent finishing" << endl;
                return true;
            }
        }
        return false;
    }
}
  

this->component->lchild->execute(); и this->component->rchild->execute(); выполнить execvp соответствующие команды. Я подтвердил, что каждый из них возвращает, распечатав инструкцию в родительском процессе. Однако, на мой взгляд, Pipe::execute() похоже, что дочерний процесс не завершается, потому что оператор cout в родительском процессе никогда не печатается, и я получаю ошибку сегментации после инициализации запроса ( $ ) (см. Рисунок). Вот основная функция, которая инициализирует приглашение после каждого выполнения:

 int main()
{
    Prompt new_prompt;
    while(1) {
        new_prompt.initialize();
    }
    return 0;
}
  

и вот initialize() функция:

 void Prompt::initialize()
{
    cout << "$ ";
    std::getline(std::cin, input);

    parse(input);
    run();
    input.clear();
    tokens.clear();
    fflush(stdout);
    fflush(stdin);
    return;
}
  

Кажется, что ls | sort все работает нормально, но затем, когда инициализируется приглашение, getline считывает пустую строку во входные данные. Я пробовал использовать cin.clear(), cin.ignore и строки fflush и clear() выше. Эта пустая строка «анализируется», а затем вызывается run() функция, которая пытается разыменовать нулевой указатель. Есть идеи о том, почему / где эта пустая строка вводится в getline? и как мне это решить? Спасибо!

ОБНОВЛЕНИЕ: родительский процесс в канале теперь завершается. Я также заметил, что я получаю ошибки seg также для моих классов перенаправления ввода-вывода ( > и < ). Я думаю, что я неправильно очищаю поток или закрываю файловые дескрипторы…

Вот моя execute() функция для lchild и rchild:

 bool Command::execute() {
    int status;
    int errorno;
    pid_t child;
    vector<char *> argv;
    for (unsigned i=0; i < this->command.size();   i) {
        char * cstr = const_cast<char*>(this->command.at(i).c_str());
        argv.push_back(cstr);
    }
    argv.push_back(NULL);
    child = fork();
    if (-1 == child) {
        perror("fork failed");
    }
    if (child == 0) {
        errorno = execvp(*argv.data(), argv.data());
        _exit(1);

    } else if (child > 0) {
        waitpid(child, amp;status, 0);
        if ( WIFEXITED(status) ) {
            //printf("child exited with = %dn",WEXITSTATUS(status));
            if ( WEXITSTATUS(status) == 0) {
                //cout << "command parent finishing" << endl;
                return true;
            }
        }
        return false;
    }
}
  

Комментарии:

1. Предполагается, что это похоже на приглашение терминала оболочки — оно ожидает ввода пользователем

2. Независимо от того, что на самом деле вызывает пустую строку, я бы рекомендовал программе не выполнять segfault при запуске через один — выполняйте проверки и выдавайте ошибку как можно скорее, а не выполняйте segfault.

3. После обновления это другой вопрос, который требует других частей кода. Я подозреваю, что там аналогичная проблема, но не могу сказать, не видя кода

4. @MichaelVeksler Я добавил функцию execute. Эта функция должна работать и для других типов команд, поэтому я боюсь закрыть один из потоков в ней (например, если я запускаю echo hello amp; amp; pwd, я не хочу ничего закрывать — только если я использую перенаправление)

Ответ №1:

Вот ошибка:

 else if (child > 0) {
    dup2(fds[0], STDIN_FILENO);
    close(fds[0]);
    close(fds[1]);
    this->component->rchild->execute();
  

Вы закрываете stdin для родительского, а не только для нужного дочернего элемента. После этого stdin родительского процесса такой же, как и у правильного дочернего процесса.

После этого

 std::getline(std::cin, input);
  

Пытается прочитать выходные данные левого дочернего элемента, а не исходного stdin. К этому моменту левый дочерний элемент был завершен, и этот конец канала был закрыт. Это приводит к сбою чтения stdin и оставляет input неизменным его исходное состояние.

Редактировать: Ваш дизайн имеет незначительные и серьезные недостатки. Незначительный недостаток заключается в том, что вам не нужна вилка в Pipe::execute . Основной недостаток заключается в том, что дочерний элемент должен перенаправлять потоки и закрывать дескрипторы.

Просто передайте входные и выходные параметры через fork() и позвольте дочернему элементу дублировать их. Не забудьте также закрыть несвязанные концы канала. Если вы этого не сделаете, левый дочерний файл завершится, но его выходной канал продолжит работу в других процессах. Пока существуют другие копии этого дескриптора, правильный потомок никогда не получит EOF при чтении своего блока конца канала и недели навсегда.

Комментарии:

1. Ааааа, понятно. Итак, есть ли способ «повторно открыть» stdin? Или мне нужно где-то дополнительно закрыть?

2. @SarahAllec вы должны вызывать close в самом правильном дочернем элементе, а не в родительском

3. Ну, на самом деле, похоже, что правильного дочернего элемента не существует , поскольку есть только один вызов fork() . Один родительский элемент, один (левый) дочерний элемент. По крайней мере, это то, что существует на уровне class Pipe .

4. @BenVoigt посмотрите на обновленный вопрос. Как я и подозревал, метод execute вызывает fork , и это правильный дочерний элемент. Я просто удалил свою заметку об этом предположении после того, как вопрос был прояснен

5. И теперь есть 3 дочерних элемента, что по-прежнему не соответствует ни количеству fork() вызовов, ни управлению каналом в правильных дочерних процессах.