Взаимодействие между каналами, execvpe и сценариями оболочки

#c #linux #shell #posix

#c #linux #оболочка #posix

Вопрос:

У меня есть следующий код, который разветвляет и execvpe сценарий оболочки и перенаправляет его STDERR и STDOUT родительскому процессу.

 #define _GNU_SOURCE

#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define BUFLEN 65536

int main () {
  int pipes[2];
  pipe(pipes);
  pid_t pid = fork();
  if (pid == -1) {return 1;}
  else if (pid > 0) {
    close(pipes[1]);
    char buf[BUFLEN];
    size_t n = read(pipes[0], buf, sizeof(buf));
    int stat = 0;
    waitpid(pid, amp;stat, 0);
    printf("%.*s", n, buf);
  } else {
    dup2(pipes[1], STDOUT_FILENO);
    dup2(pipes[1], STDERR_FILENO);
    close(pipes[0]);
    char *const argv[] = {"sh", "test", NULL};
    execvpe(argv[0], argv, environ);
  }

  return 0;
}
  

В качестве минимального рабочего примера "test" :

 #!/bin/bash
cat file.txt
echo "Hello!"
echo "Goodbye!"
  

Выводом программы C является содержимое file.txt , а затем выходные данные эхо-сигналов теряются. Если это три оператора echo, то все они будут видны.

Мое лучшее предположение заключается в том, что echo — это встроенная оболочка, и оболочка будет разветвляться cat , и мои каналы будут потеряны. В моем проекте кажется, что первая команда вызывается в скрипте, а остальные теряются.

Если мое предположение верно, как я могу собрать все выходные данные от всех дочерних элементов того, что execvpe породило?

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

1. Вам нужно читать в цикле, пока read не вернется 0 (что означает, что другой конец канала был закрыт). Или, конечно, до тех пор, пока не появится ошибка (при read возврате -1 ).

Ответ №1:

Я думаю, что проблема заключается просто в сочетании синхронизации и отсутствии проверки EOF перед остановкой чтения из канала. Если вы переносите read() вызов в цикл для чтения всего, считываются все данные. Когда cat завершается, read() возвращает все, что доступно. Выходные данные echo команд впоследствии добавляются в канал, но просто не читаются.

Я полагаю, что этот код демонстрирует. Обратите внимание, что execvpe() это не стандарт POSIX (и, в частности, недоступно в macOS), поэтому я использовал свой собственный суррогатный заголовок #include "execvpe.h" и реализацию execvpe.c , чтобы получить его реализацию. Кроме того, POSIX не определяет заголовок, который объявляет environ , поэтому я тоже его объявил. Вероятно, вы используете Linux, и системные заголовки там исправляют некоторые пробелы, которые POSIX оставляет в виде дыр.

Вот рабочий код и данные.

pipe17.c

 /* SO 6412-3757 */
#define _GNU_SOURCE

#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>

#define BUFLEN 65536

#include "execvpe.h"    /* execvpe() is not in POSIX */
extern char **environ;  /* No POSIX header declares environ */

int main(void)
{
    int pipes[2];
    pipe(pipes);
    pid_t pid = fork();
    if (pid == -1)
    {
        return 1;
    }
    else if (pid > 0)
    {
        close(pipes[1]);
        char buffer[BUFLEN];
        char *b_str = buffer;
        size_t b_len = sizeof(buffer);
        size_t t_len = 0;
        ssize_t n_len;
        while (b_len > 0 amp;amp; (n_len = read(pipes[0], b_str, b_len)) > 0)
        {
            b_str  = n_len;
            b_len -= n_len;
            t_len  = n_len;
        }
        close(pipes[0]);
        int status = 0;
        int corpse = waitpid(pid, amp;status, 0);
        printf("%d 0x%.4X: [[%.*s]]n", corpse, status, (int)t_len, buffer);
    }
    else
    {
        dup2(pipes[1], STDOUT_FILENO);
        dup2(pipes[1], STDERR_FILENO);
        close(pipes[0]);
        close(pipes[1]);
        char *argv[] = {"sh", "test", NULL};
        execvpe(argv[0], argv, environ);
        fprintf(stderr, "failed to execute '%s'n", argv[0]);
        exit(1);
    }

    return 0;
}
  

test

 #!/bin/bash
cat file.txt
echo "Hello!"
echo "Goodbye!"
echo "Errors go to standard error" >amp;2
  

file.txt

 Line 1 of file1.txt
The very last line of file1.txt
  

Пример вывода

 14171 0x0000: [[Line 1 of file1.txt
The very last line of file1.txt
Hello!
Goodbye!
Errors go to standard error
]]
  

Обратите внимание, что код закрывает оба конца канала перед вызовом execvpe() . Здесь это не имеет решающего значения, но часто может иметь решающее значение для этого. Ваш исходный код передал size_t значение, n , printf() для использования * в формате. Вам может сойти с рук это на 64-разрядной машине where sizeof(int) == sizeof(size_t) , но это выдает предупреждения о компиляции на 64-разрядных машинах where sizeof(int) < sizeof(size_t) .


Эмпирическое правило: если dup2() один конец канала подключен к стандартному вводу или стандартному выводу, закройте оба исходных файловых дескриптора, возвращенных pipe() как можно скорее. В частности, вы должны закрыть их перед использованием любой из exec*() семейства функций.

Правило также применяется, если вы дублируете дескрипторы с помощью или dup() или fcntl() с F_DUPFD помощью или F_DUPFD_CLOEXEC .

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

1. Это очень подробный ответ, и цикл был проблемой в моем более запутанном коде проекта (который передает int в printf phew ). И вы правы, программное обеспечение, которое я разрабатываю, предназначено специально для Linux Спасибо!