Можно ли читать и писать в дескриптор файла, возвращенный popen. У меня есть интерактивный процесс, который я бы хотел контролировать с помощью C. Если это невозможно с помощью popen, есть ли какой-нибудь способ?
Одновременное чтение и запись
Ответ 1
Как уже было сказано, popen работает в одном направлении. Если вам нужно читать и писать, вы можете создать канал с помощью pipe(), развернуть новый процесс с помощью функций fork() и exec, а затем перенаправить свои входы и выходы с помощью dup2(). Во всяком случае, я предпочитаю exec поверх popen, так как он дает вам лучший контроль над процессом (например, вы знаете его pid)
Редакция:
Как следует из комментариев, труба может использоваться только в одном направлении. Поэтому вам нужно создавать отдельные каналы для чтения и записи. Поскольку предыдущий пример был неправильным, я удалил его и создал новый, правильный:
#include<unistd.h>
#include<sys/wait.h>
#include<sys/prctl.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main(int argc, char** argv)
{
pid_t pid = 0;
int inpipefd[2];
int outpipefd[2];
char buf[256];
char msg[256];
int status;
pipe(inpipefd);
pipe(outpipefd);
pid = fork();
if (pid == 0)
{
// Child
dup2(outpipefd[0], STDIN_FILENO);
dup2(inpipefd[1], STDOUT_FILENO);
dup2(inpipefd[1], STDERR_FILENO);
//ask kernel to deliver SIGTERM in case the parent dies
prctl(PR_SET_PDEATHSIG, SIGTERM);
//replace tee with your process
execl("/usr/bin/tee", "tee", (char*) NULL);
// Nothing below this line should be executed by child process. If so,
// it means that the execl function wasn't successfull, so lets exit:
exit(1);
}
// The code below will be executed only by parent. You can write and read
// from the child using pipefd descriptors, and you can send signals to
// the process using its pid by kill() function. If the child process will
// exit unexpectedly, the parent process will obtain SIGCHLD signal that
// can be handled (e.g. you can respawn the child process).
//close unused pipe ends
close(outpipefd[0]);
close(inpipefd[1]);
// Now, you can write to outpipefd[1] and read from inpipefd[0] :
while(1)
{
printf("Enter message to send\n");
scanf("%s", msg);
if(strcmp(msg, "exit") == 0) break;
write(outpipefd[1], msg, strlen(msg));
read(inpipefd[0], buf, 256);
printf("Received answer: %s\n", buf);
}
kill(pid, SIGKILL); //send SIGKILL signal to the child process
waitpid(pid, &status, 0);
}
Ответ 2
Причина popen()
и друзья не предлагают двунаправленную связь, так это то, что она будет тупиковой, из-за буферизации в подпроцессе. Все временные трубопроводы и решения socketpair()
, обсуждаемые в ответах, страдают от одной и той же проблемы.
В UNIX большинству команд нельзя доверять, чтобы читать одну строку и немедленно обрабатывать ее и печатать, за исключением случаев, когда их стандартный вывод является tty. Причина в том, что stdio-буферы выводятся в пользовательском пространстве по умолчанию и отменяют системный вызов write()
до тех пор, пока не будет заполнен буфер или поток stdio не будет закрыт (как правило, из-за того, что программа или script собирается выйти после просмотра EOF вход). Если вы пишете в такую программу stdin через трубу и теперь ждите ответа от этой программы stdout (без закрытия входящего канала), ответ застрял в буферах stdio и никогда не выйдет - это тупик.
Вы можете обмануть некоторые линейно ориентированные программы (например, grep
), чтобы не буферизировать, используя псевдо-tty, чтобы разговаривать с ними; посмотрите libexpect(3)
. Но в общем случае вам придется повторно запустить другой подпроцесс для каждого сообщения, позволяя использовать EOF для сигнализации конца каждого сообщения и вызывать сброс всех буферов в команде (или конвейере команд). Очевидно, что это не очень хорошая работа.
Подробнее об этой проблеме читайте на странице perlipc (для двунаправленных каналов в Perl, но соображения буферизации применяются независимо от языка, используемого для основная программа).
Ответ 3
Вы хотите что-то часто называемое popen2. Здесь базовая реализация без проверки ошибок.
Ответ 4
popen()
может открыть канал только в режиме чтения или записи, а не в обоих. Посмотрите этот поток для обхода.
Ответ 5
Используйте forkpty
(он нестандартен, но API очень приятный, и вы всегда можете отказаться от своей собственной реализации, если у вас его нет) и exec
программу, с которой вы хотите общаться в дочерний процесс.
В качестве альтернативы, если семантика tty вам не по душе, вы можете написать что-то вроде forkpty
, но используя два канала: по одному для каждого направления связи или используя socketpair
для связи с внешней программой через unix-сокет.
Ответ 6
Вы не можете использовать popen
для использования двухсторонних каналов.
Фактически, некоторые ОС не поддерживают двусторонние каналы, и в этом случае единственная возможность - это сокет-пара (socketpair
).
Ответ 7
В одном из netresolve backends Я разговариваю с script, поэтому мне нужно написать его stdin
и читать из stdout
. Следующая функция выполняет команду с stdin и stdout, перенаправленными на канал. Вы можете использовать его и адаптировать по своему вкусу.
static bool
start_subprocess(char *const command[], int *pid, int *infd, int *outfd)
{
int p1[2], p2[2];
if (!pid || !infd || !outfd)
return false;
if (pipe(p1) == -1)
goto err_pipe1;
if (pipe(p2) == -1)
goto err_pipe2;
if ((*pid = fork()) == -1)
goto err_fork;
if (*pid) {
/* Parent process. */
*infd = p1[1];
*outfd = p2[0];
close(p1[0]);
close(p2[1]);
return true;
} else {
/* Child process. */
dup2(p1[0], 0);
dup2(p2[1], 1);
close(p1[0]);
close(p1[1]);
close(p2[0]);
close(p2[1]);
execvp(*command, command);
/* Error occured. */
fprintf(stderr, "error running %s: %s", *command, strerror(errno));
abort();
}
err_fork:
close(p2[1]);
close(p2[0]);
err_pipe2:
close(p1[1]);
close(p1[0]);
err_pipe1:
return false;
}
https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46
(Я использовал тот же код в Может ли popen() создавать двунаправленные каналы, например pipe() + fork()?)
Ответ 8
popen работает для меня в обоих направлениях (чтение и запись)
Я использовал трубку popen()
в обоих направлениях.
Чтение и запись дочернего процесса stdin
и stdout
с файловым дескриптором, возвращаемым popen (команда, "w" )
Кажется, что все нормально.
Я предположил, что это сработает, прежде чем я узнаю, и это так. По сообщениям выше это не должно работать. Меня это немного беспокоит.
gcc на raspbian (raspbery pi debian)