子进程的异步双向IO重定向

时间:2012-03-03 22:12:53

标签: c++ python c linux io-redirection

我试图找出子进程的异步双向IO重定向的通用方法。基本上,我想生成一个等待输入的交互式子进程,应该回读任何输出。我尝试通过生成一个新的python进程来试验python.subprocess。试图实现的基本简化示例如下

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套接字,但为此我必须改变子进程,这可能是不可行的。有没有其他方法可以实现它?

注意***我的最终目标是创建一个可以与远程Web客户端交互的服务器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 [] [] []

仅供参考, 正在运行python

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

似乎工作正常。

我还想出了一个'C'代码。但结果并没有什么不同。

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;
}

5 个答案:

答案 0 :(得分:1)

有不同的方法可以做到这一点。 例如,您可以:

  • 使用SysV消息队列并在队列上使用超时轮询消息到达
  • 为子节点创建一个pipe(),为父节点创建一个pipe(),使用O_NONBLOCK标志,然后在文件描述符上选择()以获取数据(如果没有数据到达,甚至可以处理超时)
  • 使用socket()AF_UNIX或AF_INET,将其设置为非阻塞,选择()或epoll()以使数据到达
  • mmap()MAP_SHARED内存段并在数据到达时发出其他进程的信号,注意带锁定机制的共享段。

我在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[] = "0123456789abcdefghijklmnopqrstuvwxyz";
        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;
}

答案 1 :(得分: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可执行文件不产生输出。第二个是存在竞争条件,因为你可以在子进程产生输出之前连续5次调用select(),在这种情况下,你会在读取任何内容之前终止进程。

我解决了上面提到的三个问题(写入列表,启动产生输出和竞争条件的过程)。试试这个样本,看看它是否适合你:

#!/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()代替。


  

仅供参考,将python作为

运行
/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

如果将除控制tty(键盘)以外的任何重定向为进程的标准输入,则不会从python解释器获得任何输出。这就是您的代码无法正常工作的原因。

答案 2 :(得分:0)

我在bash中使用2-way io:

mkfifo hotleg
mkfifo coldleg

program <coldleg |tee hotleg &

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

(请注意,您可以只使用“&gt;”代替T恤,但您可能希望首先看到输出)

答案 3 :(得分:0)

你猜测缓冲I / O是非常可能的。你编写循环的方式,读取将阻塞,直到它填满所需的缓冲区,并且在返回之前你将无法处理任何输入。这很容易造成僵局。

Popen.communicate处理这个问题,方法是让一个线程与每个管道一起工作,并确保它有所有要写入stdin的数据,以便在文件对象等待缓冲区时不能延迟实际写入填充或要刷新/关闭文件对象。我认为如果你需要,你可以制定一个涉及线程工作的解决方案,但这不是非同步的,可能不是最简单的解决方案。

你可以通过不使用Popen提供的文件对象访问管道来解决python的缓冲问题,而是使用fileno()方法获取它们的fd。然后,您可以将fd与os.read,os.write和select.select一起使用。 os.read和os.write函数不会进行缓冲,但它们会阻塞,直到至少可以读取/写入一个字节。在调用管道之前,您需要确保管道是可读/可写的。最简单的方法是使用select.select()等待所有想要读/写的管道,并对select()返回时准备好的每个管道进行一次读或写调用。如果搜索,你应该能够找到select循环的例子(他们可能会使用套接字而不是管道,但原理是相同的)。 (另外,如果没有先检查它是否会阻塞,就不要进行读取或写入,或者最终会遇到导致子进程死锁的情况。即使没有,也必须准备好读取数据写下你想要的一切。)

答案 4 :(得分:0)

如果您需要控制Python解释器会话,那么最好使用

在后一种情况下,服务器可以在任何地方运行,PyScripter已经有一个工作服务器模块(客户端模块在Pascal中,需要翻译)。