草庐IT

c++ - 子进程的异步双向 IO 重定向

coder 2023-06-21 原文

我正在尝试为子进程的异步双向 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;
}

最佳答案

在您发布的 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 Hello, world!

After Terminating

请注意,出于某种原因,在 select.select() 中使用 timeout 参数并没有在我的系统上产生预期的结果,我求助于使用 time.sleep() 代替。


Just FYI, running python as

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

seems to be working just fine.

你不能得到这个效果,因为这个例子仍然给了 python 解释器一个控制 tty。没有控制 tty,python 解释器不会打印 Python 版本,也不会显示 >>> 提示符。

一个接近的例子如下所示。您可以将 /dev/null 替换为包含要发送给解释器的命令的文件。

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

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

关于c++ - 子进程的异步双向 IO 重定向,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9550338/

有关c++ - 子进程的异步双向 IO 重定向的更多相关文章

  1. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  3. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  4. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  5. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  6. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  7. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

    我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

  8. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  9. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  10. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

随机推荐