#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 Спасибо!