2012-03-03 3 views
4

Я пытаюсь выяснить обобщенный способ для асинхронного двунаправленного ввода-вывода перенаправления дочернего процесса. В принципе, я хотел бы создать интерактивный дочерний процесс, ожидающий ввода, и любой вывод должен быть прочитан. Я попытался поэкспериментировать с python.subprocess, создав новый процесс python. Базовый упрощенный пример пытались достичь следующим образомАсинхронное двунаправленное перенаправление ввода-вывода для дочернего процесса

process = subprocess.Popen(['/usr/bin/python'],shell=False,stdin=subprocess.PIPE, stdout=subprocess.PIPE) 
while True: 
    output = process.stdout.readline() 
    print output 
    input = sys.stdin.readline() 
    process.stdin.write(input) 

и выполнения вышеуказанного фрагмента кода просто зависает без какого-либо вывода. Я пробовал работать с /usr/bash и /usr/bin/irb, но результат все тот же. Я предполагаю, что буферизованный IO просто не хорошо гелеобразован с перенаправлением IO.

Итак, мой вопрос в том, можно ли читать выходные данные дочернего процесса без промывки буфера или выхода из подпроцесса?

following post упоминает сокеты IPC, но для этого мне пришлось бы изменить дочерний процесс, который может оказаться невозможным. Есть ли другой способ его достижения?

Примечание *** Моя конечная цель - создать серверный REPL-процесс, который может взаимодействовать с удаленным веб-клиентом. Хотя приведенный пример относится к Python, моей конечной целью является завершение всего доступного REPL обобщенной оболочкой.


С помощью некоторых из предложения в ответах я придумал следующую

#!/usr/bin/python 
import subprocess, os, select 
proc = subprocess.Popen(['/usr/bin/python'],shell=False,stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE) 
for i in xrange(0,5): 
    inputready, outputready, exceptready = select.select([proc.stdout, proc.stderr],[proc.stdout, proc.stderr],[proc.stdout, proc.stderr],0) 
    if not inputready: print "No Data", 
    print inputready, outputready, exceptready 
    for s in inputready: print s.fileno(),s.readline() 
proc.terminate() 
print "After Terminating" 
for i in xrange(0,5): 
    inputready, outputready, exceptready = select.select([proc.stdout, proc.stderr],[proc.stdout, proc.stderr],[proc.stdout, proc.stderr],0) 
    if not inputready: print "No Data", 
    print inputready, outputready, exceptready 
    for s in inputready: print s.fileno(),s.readline() 

сейчас, хотя эти программы не в тупик, но, к сожалению, там нет выхода. Запуск выше код я получаю

No Data [] [] [] 
No Data [] [] [] 
No Data [] [] [] 
No Data [] [] [] 
No Data [] [] [] 
After Terminating 
No Data [] [] [] 
No Data [] [] [] 
No Data [] [] [] 
No Data [] [] [] 
No Data [] [] [] 

Просто FYI, работает питона, как

/usr/bin/python 2>&1|tee test.out 

, кажется, работает нормально.

Я также придумал код «С». Но результат не отличается.

int kbhit() { 
    struct timeval tv; 
    fd_set fds; 
    tv.tv_sec = tv.tv_usec = 0; 
    FD_ZERO(&fds); 
    FD_SET(STDIN_FILENO, &fds); 
    select(STDIN_FILENO+1, &fds, NULL, NULL, &tv); 
    return FD_ISSET(STDIN_FILENO, &fds); 
} 
void receive(char *str) { 
    char ch; 
    fprintf(stderr,"IN1\n"); 
    if(!kbhit()) return; 
    fprintf(stderr,"IN2\n"); 
    fprintf(stderr,"%d\n",kbhit()); 
    for(;kbhit() && (ch=fgetc(stdin))!=EOF;) { 
     fprintf(stderr,"%c,%d",ch,kbhit()); 
    } 
    fprintf(stderr,"Done\n"); 
} 
int main(){ 
    pid_t pid; 
    int rv, pipeP2C[2],pipeC2P[2]; 
    pipe(pipeP2C); 
    pipe(pipeC2P); 
    pid=fork(); 
    if(pid){ 
     dup2(pipeP2C[1],1); /* Replace stdout with out side of the pipe */ 
     close(pipeP2C[0]); /* Close unused side of pipe (in side) */ 
     dup2(pipeC2P[0],0); /* Replace stdin with in side of the pipe */ 
     close(pipeC2P[1]); /* Close unused side of pipe (out side) */ 
     setvbuf(stdout,(char*)NULL,_IONBF,0); /* Set non-buffered output on stdout */ 
     sleep(2); 
     receive("quit()\n"); 
     wait(&rv);    /* Wait for child process to end */ 
     fprintf(stderr,"Child exited with a %d value\n",rv); 
    } 
    else{ 
     dup2(pipeP2C[0],0); /* Replace stdin with the in side of the pipe */ 
     close(pipeP2C[1]); /* Close unused side of pipe (out side) */ 
     dup2(pipeC2P[1],1); /* Replace stdout with the out side of the pipe */ 
     close(pipeC2P[0]); /* Close unused side of pipe (out side) */ 
     setvbuf(stdout,(char*)NULL,_IONBF,0); /* Set non-buffered output on stdout */ 
     close(2), dup2(1,2); /*Redirect stderr to stdout */ 
     if(execl("/usr/bin/python","/usr/bin/python",NULL) == -1){ 
      fprintf(stderr,"execl Error!"); 
      exit(1); 
     } 
    } 
    return 0; 
} 
+4

Как правило, вы не пытаетесь дождаться ввода и генерируете выход в реальном времени в том же потоке. – stark

+0

@stark, Итак, вы хотите сказать, что то, что я намереваюсь достичь, невозможно сделать? Я бы просто попытался использовать [следующий пост] (http://stackoverflow.com/questions/9405985/linux-3-0-executing-child-process-with-piped-stdin-stdout), чтобы увидеть, будет ли это работать для меня или нет. – Abhijit

+0

Если вы оба читаете и пишите из одного потока, вы можете использовать системный вызов мультиплексирования, например http://linux.die.net/man/2/poll, например. с http://docs.python.org/library/select.html –

ответ

1

В коде Python вы в курсе, что вы не используете правильные потоки:

inputready, outputready, exceptready = select.select(
    [proc.stdout, proc.stderr], # read list 
    [proc.stdout, proc.stderr], # write list 
    [proc.stdout, proc.stderr], # error list. 
    0)       # time out. 

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


В вашем образце происходит несколько ошибок. Во-первых, исполняемый файл python, который вы запускаете как дочерний процесс, не выводит. Во-вторых, существует условие гонки, так как вы можете вызывать select() 5 раз подряд до того, как дочерний процесс произведет вывод, и в этом случае вы будете убивать процесс перед чтением чего-либо.

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

#!/usr/bin/python 
import subprocess, os, select, time 

path = "/usr/bin/python" 
proc = subprocess.Popen([path, "foo.py"], shell=False, 
         stdin=subprocess.PIPE, 
         stdout=subprocess.PIPE, 
         stderr=subprocess.PIPE) 
for i in xrange(0,5): 
    time.sleep(1) 
    inputready, outputready, exceptready = select.select(
     [proc.stdout, proc.stderr], [proc.stdin,], 
     [proc.stdout, proc.stderr, proc.stdin], 0) 
    if not inputready: 
     print "No Data", 
    print inputready, outputready, exceptready 
    for s in inputready: 
     print s.fileno(),s.readline() 

proc.terminate() 
print "After Terminating" 

for i in xrange(0,5): 
    inputready, outputready, exceptready = select.select(
     [proc.stdout, proc.stderr], [proc.stdin,], 
     [proc.stdout, proc.stderr, proc.stdin], 0) 
    if not inputready: 
     print "No Data", 
    print inputready, outputready, exceptready 
    for s in inputready: 
     print s.fileno(),s.readline() 

Файл foo.py я содержал следующее:

#!/usr/bin/python 
print "Hello, world!" 

Следующую версию (в основном удален избыточный выход, чтобы сделать результаты легче чтения):

#!/usr/bin/python 
import subprocess, os, select, time 

path = "/usr/bin/python" 
proc = subprocess.Popen([path, "foo.py"], shell=False, 
         stdin=subprocess.PIPE, 
         stdout=subprocess.PIPE, 
         stderr=subprocess.PIPE) 
for i in xrange(0,5): 
    time.sleep(1) 
    inputready, outputready, exceptready = select.select(
     [proc.stdout, proc.stderr], [proc.stdin,], 
     [proc.stdout, proc.stderr, proc.stdin], 0) 
    for s in inputready: 
     line = s.readline() 
     if line: 
      print s.fileno(), line 

proc.terminate() 
print "After Terminating" 

for i in xrange(0,5): 
    time.sleep(1) 
    inputready, outputready, exceptready = select.select(
     [proc.stdout, proc.stderr], [proc.stdin,], 
     [proc.stdout, proc.stderr, proc.stdin], 0) 
    for s in inputready: 
     line = s.readline() 
     if line: 
      print s.fileno(), line 

дает следующий результат:

5 Здравствуйте, мир!

После Нагрузочный

Обратите внимание, что по какой-то причине, используя параметр timeout в select.select() не дали ожидаемых результатов в моей системе, и я прибегал к использованию time.sleep() вместо этого.


Просто FYI, работает питона, как

/usr/bin/python 2>&1|tee test.out 

, кажется, работает нормально.

Вы не можете получить этот эффект, потому что этот пример по-прежнему дает интерпретатору python управляющий tty. Без контрольного tty интерпретатор python не печатает версию Python и не отображает приглашение >>>.

Близким примером может быть следующее. Вы можете заменить /dev/null файлом, содержащим команды для отправки интерпретатору.

/usr/bin/python </dev/null 2>&1|tee test.out 

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

+0

Я бы, вероятно, этого не хотел. Мое намерение состоит в том, чтобы взаимодействовать с Python Interpreter (в начале) с помощью скрипта/программы, а не с консоли. Я просто не могу понять, почему было бы так трудно подражать «/ usr/bin/python 2> & 1 | tee test.out'. Я просто запустил strace и прохожу через системные вызовы :-) – Abhijit

+0

В основном, я продемонстрировал, что ваш код работает в основном. Просто прекратите работу после 5 итераций, чтобы позволить дочернему процессу производить вывод. Помните, что если вы намереваетесь сделать это с помощью REPL, ваш дочерний процесс обычно производит вывод в ответ на ввод от пользователя, поэтому вам нужно будет отправить ввод, а затем ждать выхода. –

+0

Если вы хотите перенаправить на интерпретатор python двунаправленным образом, вам придется изменить цикл 'select()' так, чтобы он читал из трех потоков ввода: стандартный вывод «стандартный входной сигнал и дочерний процесс» родительского процесса » потоки. Независимо от того, что приходит со стандартного ввода, отправьте стандартный ввод процесса. –

0

Я использую 2-полосная Ио в Баш, как это:

mkfifo hotleg 
mkfifo coldleg 

program <coldleg |tee hotleg & 

while read LINE; do 
case $LINE in 
    *)call_a_function $LINE;; 
esac 
done <hotleg |tee coldleg & 

(обратите внимание, что вы можете просто «>» вместо тройника, но вы можете увидеть выход на первый)

0

Предполагается, что ваша ошибка в том, что буферизованный ввод-вывод является виной. То, как вы написали свой цикл, чтение будет блокироваться до тех пор, пока он не заполнит необходимый буфер, и вы не сможете обработать какой-либо ввод до его возвращения. Это может привести к тупиковой ситуации.

Popen.communicate занимается этим, создавая поток для работы с каждым трупом и убедившись, что все данные должны быть записаны на stdin, так что фактическая запись не может быть отложена, пока файл-объект ждет буфер для заполнения или для файлового объекта, который должен быть сброшен/закрыт. Я думаю, вы могли бы сделать решение, связанное с работой потоков, если вам нужно, но это не очень асинхронно и, вероятно, не самое простое решение.

Вы можете обойти буферизацию питона, не используя файловые объекты, предоставленные Popen для доступа к трубам, и вместо этого захватить их fd с помощью метода fileno(). Затем вы можете использовать fd с os.read, os.write и выбрать.Выбрать. Функции os.read и os.write не будут выполнять буферизацию, но они будут блокироваться, пока не будет прочитан хотя бы один байт. Перед вызовом убедитесь, что канал доступен для чтения/записи. Самый простой способ сделать это - использовать select.select() для ожидания всех каналов, которые вы хотите прочитать/записать, и сделать один вызов для чтения или записи для каждого канала, который будет готов, когда select() вернется. Вы должны уметь находить примеры выбранных циклов при поиске (они, вероятно, будут использовать сокеты вместо труб, но принцип тот же). (Кроме того, никогда не читайте и не пишите, не проверяя сначала, что он не будет блокироваться, или вы можете столкнуться с ситуациями, когда вы вызываете тупик с дочерним процессом. Вы должны быть готовы к чтению данных, даже если у вас нет все же написано все, что вы хотите.)

+0

Обратите внимание, что пример кода с использованием 'select()' делает именно это. ['select.select()'] (http://docs.python.org/library/select.html#select.select) использует '.fileno()' для нецелых объектов, переданных ему. –

1

Есть разные способы сделать это. Вы можете, например:

  • очередей использование SysV сообщения и опрос с тайм-аутом на очереди сообщений, чтобы прибыть
  • создать трубу() для ребенка и трубу() для отца и с помощью O_NONBLOCK, а затем выберите() в дескрипторах файла для получения данных (чтобы даже обрабатывать таймауты, если данные не поступают)
  • use socket() AF_UNIX или AF_INET, установите его без блокировки и выберите() или epoll() для данные должны прибыть
  • mmap() MAP_SHARED сегменты памяти и сигнализировать другой процесс, когда данные получены, обратите внимание на общий сегмент с помощью loc король механизм.

я написал образец в C с двойными трубами:

#include <stdio.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/time.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <sys/stat.h> 
#include <sys/select.h> 
#include <fcntl.h> 
#include <signal.h> 

#define BUFLEN (6*1024) 
#define EXECFILE "/usr/bin/python" 

char *itoa(int n, char *s, int b) { 
     static char digits[] = "abcdefghijklmnopqrstuvwxyz"; 
     int i=0, sign; 

     if ((sign = n) < 0) 
       n = -n; 

     do { 
       s[i++] = digits[n % b]; 
     } while ((n /= b) > 0); 

     if (sign < 0) 
       s[i++] = '-'; 
     s[i] = '\0'; 

     return s; 
} 

/* 
int set_nonblock(int sockfd) { // set socket to non blocking 
     int arg,i; 

     if ((arg=fcntl(sockfd, F_GETFL, NULL)) < 0) { 
       printf("error getting socket flag for fd %i: fcntl(..., F_GETFL): %i\n", sockfd, errno); 
       return -1; 
     } 
     // set O_NONBLOCK flag 
     arg |= O_NONBLOCK; 
     if ((i=fcntl(sockfd, F_SETFL, arg)) < 0) { 
       printf("error setting socket flag for fd %i: fcntl(..., F_SETFL): %i\n", sockfd, errno); 
       return -1; 
     } 
     return i; 
} 

int set_block(int sockfd) { // set socket to blocking 
     int arg,i; 

     if ((arg=fcntl(sockfd, F_GETFL, NULL)) < 0) { 
       printf("error getting socket flag for fd %i: fcntl(..., F_GETFL): %i\n", sockfd, errno); 
       return -1; 
     } 
     // clean O_NONBLOCK flag 
     arg &= (~O_NONBLOCK); 
     if ((i=fcntl(sockfd, F_SETFL, arg)) < 0) { 
       printf("error setting socket flag for fd %i: fcntl(..., F_SETFL): %i\n", sockfd, errno); 
       return -1; 
     } 
     return i; 
} 
*/ 
int main() { 
     FILE *input; 
     char slice[BUFLEN]; 
     int status = 0; 
     pid_t pid; 
     int err; 
     int newfd; 
     // if you want you can pass arguments to the program to execute 
     // char *const arguments[] = {EXECFILE, "-v", NULL}; 
     char *const arguments[] = {EXECFILE, NULL}; 
     int father2child_pipefd[2]; 
     int child2father_pipefd[2]; 
     char *read_data = NULL; 
     FILE *retclam; 
     fd_set myset; 
     int x=1; 

     signal(SIGPIPE, SIG_IGN); 
     newfd = dup(0); 
     input = fdopen(newfd, "r"); 

     pipe(father2child_pipefd); // Father speaking to child 
     pipe(child2father_pipefd); // Child speaking to father 

     pid = fork(); 
     if (pid > 0) { // Father 
       close(father2child_pipefd[0]); 
       close(child2father_pipefd[1]); 

       // Write to the pipe reading from stdin 
       retclam = fdopen(child2father_pipefd[0], "r"); 


       // set the two fd non blocking 
       //set_nonblock(0); 
       //set_nonblock(child2father_pipefd[0]); 
       //set_nonblock(fileno(retclam)); 

       while(x==1) { 
         // clear the file descriptor set 
         FD_ZERO(&myset); 
         // add the stdin to the set 
         FD_SET(fileno(input), &myset); 
         // add the child pipe to the set 
         FD_SET(fileno(retclam), &myset); 

         // here we wait for data to arrive from stdin or from the child pipe. The last argument is a timeout, if you like 
         err = select(fileno(retclam)+1, &myset, NULL, NULL, NULL); 
         switch(err) { 
         case -1: 
           // Problem with select(). The errno variable knows why 
           //exit(1); 
           x=0; 
           break; 
         case 0: 
           // timeout on select(). Data did not arrived in time, only valid if the last attribute of select() was specified 
           break; 
         default: 
           // data is ready to be read 
           bzero(slice, BUFLEN); 
           if (FD_ISSET(fileno(retclam), &myset)) { // data ready on the child 
             //set_block(fileno(retclam)); 
             read_data = fgets(slice, BUFLEN, retclam); // read a line from the child (max BUFLEN bytes) 
             //set_nonblock(fileno(retclam)); 
             if (read_data == NULL) { 
               //exit(0); 
               x=0; 
               break; 
             } 
             // write data back to stdout 
             write (1, slice, strlen(slice)); 
             if(feof(retclam)) { 
               //exit(0); 
               x=0; 
               break; 
             } 
             break; 
           } 
           bzero(slice, BUFLEN); 
           if (FD_ISSET(fileno(input), &myset)) { // data ready on stdin 
             //printf("father\n"); 
             //set_block(fileno(input)); 
             read_data = fgets(slice, BUFLEN, input); // read a line from stdin (max BUFLEN bytes) 
             //set_nonblock(fileno(input)); 
             if (read_data == NULL) { 
               //exit (0); 
               close(father2child_pipefd[1]); 
               waitpid(pid, &status, 0); 
               //fclose(input); 
               break; 
             } 
             // write data to the child 
             write (father2child_pipefd[1], slice, strlen(slice)); 
             /* 
             if(feof(input)) { 
               exit(0); 
             }*/ 
             break; 
           } 
         } 
       } 

       close(father2child_pipefd[1]); 
       fclose(input); 
       fsync(1); 
       waitpid(pid, &status, 0); 

       // child process terminated 
       fclose (retclam); 

       // Parse output data from child 
       // write (1, "you can append somethind else on stdout if you like"); 
       if (WEXITSTATUS(status) == 0) { 
         exit (0); // child process exited successfully 
       } 
     } 

     if (pid == 0) { // Child 
       close (0); // stdin is not needed 
       close (1); // stdout is not needed 
       // Close the write side of this pipe 
       close(father2child_pipefd[1]); 
       // Close the read side of this pipe 
       close(child2father_pipefd[0]); 

       // Let's read on stdin, but this stdin is associated to the read pipe 
       dup2(father2child_pipefd[0], 0); 
       // Let's speak on stdout, but this stdout is associated to the write pipe 
       dup2(child2father_pipefd[1], 1); 

       // if you like you can put something back to the father before execve 
       //write (child2father_pipefd[1], "something", 9); 
       //fsync(child2father_pipefd[1]); 
       err = execve(EXECFILE, arguments, NULL); 

       // we'll never be here again after execve succeeded!! So we get here only if the execve() failed 
       //fprintf(stderr, "Problem executing file %s: %i: %s\n", EXECFILE, err, strerror(errno)); 
       exit (1); 
     } 

     if (pid < 0) { // Error 
       exit (1); 
     } 

     fclose(input); 

     return 0; 
} 
+0

К сожалению, изменение программы для детей не является для меня вариантом. – Abhijit

+0

Да. Это определенно вариант.Вам не нужно менять дочернюю программу, но вы должны использовать fork, обрабатывать двунаправленный ввод-вывод на дочернем/отце и присоединять STDIN и STDOUT вашего ребенка к механизму ввода/вывода (например, pipe). После установки stdin и stdout вашего дочернего процесса вы должны выполнить нужную вам программу (например, python). I0m пишут образец программы с вами в C. – dAm2K

+0

Проверьте пример, который я написал для вас на C: http://pastebin.com/iGmdg0uL – dAm2K

0

Если вам нужно контролировать интерпретатора Python сессии, вы, вероятно, лучше с

Btw в последнем случае сервер может работать в любом месте, а PyScripter уже имеет рабочий серверный модуль (клиентский модуль находится в Pascal, его нужно будет перевести).

Смежные вопросы