Python 如何使用subprocess.Popen通过管道连接多个进程?

Python 如何使用subprocess.Popen通过管道连接多个进程?,python,pipe,subprocess,Python,Pipe,Subprocess,如何使用Python模块执行以下shell命令 输入数据将来自字符串,因此我实际上不需要echo。我已经做了这么多了,有人能解释一下我是如何让它也通过排序的吗 p_awk = subprocess.Popen(["awk","-f","script.awk"], stdin=subprocess.PIPE, stdout=file("outfile.txt", "w")) p_awk.comm

如何使用Python模块执行以下shell命令

输入数据将来自字符串,因此我实际上不需要
echo
。我已经做了这么多了,有人能解释一下我是如何让它也通过
排序的吗

p_awk = subprocess.Popen(["awk","-f","script.awk"],
                          stdin=subprocess.PIPE,
                          stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )
更新:请注意,虽然下面被接受的答案实际上并没有回答所问的问题,但我相信S.洛特是对的,最好不要首先解决这个问题

很好地涵盖了这一点。你有没有不明白的地方


您的程序可能非常类似,但是第二个
Popen
将stdout=设置为一个文件,并且您不需要它的
.communicate()
的输出,您会对以下内容感到高兴

import subprocess

awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
    stdin=subprocess.PIPE, shell=True )
awk_sort.communicate( b"input data\n" )
将部分工作委托给shell。让它用管道连接两个进程

将“script.awk”重写为Python,消除awk和管道,您会更高兴

编辑。一些原因表明awk没有帮助

[有太多理由通过评论作出回应。]

  • Awk正在添加一个没有重大价值的步骤。awk的处理没有Python无法处理的独特之处

  • 对于大型数据集,从awk到sort的流水线可能会缩短处理时间。对于较短的数据集,它没有显著的好处。
    awk>文件的快速测量;排序文件
    awk | sort
    将显示并发性的帮助。对于sort,它很少有帮助,因为sort不是一个一次性过滤器

  • “Python-to-sort”处理(而不是“Python-to-awk-to-sort”)的简单性阻止了这里提出的确切问题

  • Python——虽然比awk更冗长——也是显式的,因为awk有一些对新手来说不透明的隐式规则,而对非专家来说则很混乱

  • Awk(与shell脚本本身一样)添加了另一种编程语言。如果所有这些都可以用一种语言(Python)完成,那么消除shell和awk编程就可以消除两种编程语言,从而使人们能够专注于任务中产生价值的部分

  • 底线:awk不能增加显著的价值。在这种情况下,awk是一种净成本;它增加了足够的复杂性,因此有必要提出这个问题。删除awk将是一个净收益

    侧栏为什么构建管道(
    a | b
    )如此困难

    当外壳遇到
    a | b
    时,它必须执行以下操作

    import subprocess
    
    awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
        stdin=subprocess.PIPE, shell=True )
    awk_sort.communicate( b"input data\n" )
    
  • Fork是原始shell的子进程。这最终将成为b

  • 构建一个操作系统管道。(不是Python子进程.PIPE),而是调用
    os.PIPE()
    ,它返回两个通过公共缓冲区连接的新文件描述符。在这一点上,进程有来自其父进程的stdin、stdout、stderr,加上一个文件,该文件将是“a的stdout”和“b的stdin”

  • 叉一个孩子。孩子用新的a的stdout替换其stdout。执行
    a
    过程

  • b子级关闭用新b的stdin替换其stdin。执行
    b
    过程

  • b子级等待a完成

  • 父级正在等待b完成

  • 我认为上面的代码可以递归地生成
    a | b | c
    ,但是您必须隐式地将长管道括起来,将它们视为
    a |(b | c)

    由于Python有
    os.pipe()
    os.exec()
    os.fork()
    ,您可以替换
    sys.stdin
    sys.stdout
    ,因此纯Python中有一种方法可以实现上述功能。实际上,您可以使用
    os.pipe()
    subprocess.Popen
    来计算一些快捷方式


    但是,将该操作委托给shell更容易。

    要模拟shell管道:

    import subprocess
    
    some_string = b'input_data'
    
    sort_out = open('outfile.txt', 'wb', 0)
    sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
    subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in, 
                     stdin=subprocess.PIPE).communicate(some_string)
    
    from subprocess import check_call
    
    check_call('echo "input data" | a | b > outfile.txt', shell=True)
    
    不调用shell(请参阅):

    提供了一些语法:

    #!/usr/bin/env python
    from plumbum.cmd import a, b # magic
    
    (a << "input data" | b > "outfile.txt")()
    
    是:

    #/usr/bin/env python
    从plumbum.cmd导入awk,排序
    (awk[“-f”,“script.awk”]“outfile.txt”)()
    
    编辑:
    管道
    在Windows上可用,但至关重要的是,在Windows上似乎无法实际工作。见下面的评论

    Python标准库现在包括用于处理此问题的
    管道
    模块:

    ,


    我不确定这个模块已经存在多久了,但这种方法似乎比受@Cristian的答案启发而使用
    子流程要简单得多。我遇到了同样的问题,但命令不同。因此,我举了一个经过测试的例子,我相信这可能会有所帮助:

    grep_proc = subprocess.Popen(["grep", "rabbitmq"],
                                 stdin=subprocess.PIPE, 
                                 stdout=subprocess.PIPE)
    subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
    out, err = grep_proc.communicate()
    
    这是经过测试的

    做了些什么
    • 使用管道中的stdin声明惰性
      grep
      执行。当管道充满
      ps
      标准液时,将在执行
      ps
      命令时执行此命令
    • 调用主命令
      ps
      ,stdout指向
      grep
      命令使用的管道
    • 格雷普通过电话联系从管道里取下标准液

    我喜欢这种方式,因为它是用
    子流程
    接口轻轻包裹起来的自然管道概念。

    前面的答案忽略了一个要点。正如geocar所指出的,这基本上是正确的。在管道的最后一个元素上运行
    通信
    几乎就足够了

    剩下的问题是将输入数据传递到管道。对于多个子流程,最后一个元素上的一个简单的
    通信(输入数据)
    不起作用-它永远挂起。您需要手动创建管道和子管道,如下所示:

    import os
    import subprocess
    
    input = """\
    input data
    more input
    """ * 10
    
    rd, wr = os.pipe()
    if os.fork() != 0: # parent
        os.close(wr)
    else:              # child
        os.close(rd)
        os.write(wr, input)
        os.close(wr)
        exit()
    
    p_awk = subprocess.Popen(["awk", "{ print $2; }"],
                             stdin=rd,
                             stdout=subprocess.PIPE)
    p_sort = subprocess.Popen(["sort"], 
                              stdin=p_awk.stdout,
                              stdout=subprocess.PIPE)
    p_awk.stdout.close()
    out, err = p_sort.communicate()
    print (out.rstrip())
    
    现在,子级通过管道提供输入,父级调用communicate(),这将按预期工作。用这个
    #!/usr/bin/env python
    from plumbum.cmd import awk, sort
    
    (awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()
    
    grep_proc = subprocess.Popen(["grep", "rabbitmq"],
                                 stdin=subprocess.PIPE, 
                                 stdout=subprocess.PIPE)
    subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
    out, err = grep_proc.communicate()
    
    import os
    import subprocess
    
    input = """\
    input data
    more input
    """ * 10
    
    rd, wr = os.pipe()
    if os.fork() != 0: # parent
        os.close(wr)
    else:              # child
        os.close(rd)
        os.write(wr, input)
        os.close(wr)
        exit()
    
    p_awk = subprocess.Popen(["awk", "{ print $2; }"],
                             stdin=rd,
                             stdout=subprocess.PIPE)
    p_sort = subprocess.Popen(["sort"], 
                              stdin=p_awk.stdout,
                              stdout=subprocess.PIPE)
    p_awk.stdout.close()
    out, err = p_sort.communicate()
    print (out.rstrip())
    
    from tempfile import TemporaryFile
    tf = TemporaryFile()
    tf.write(input)
    tf.seek(0, 0)
    
    #!/usr/bin/env python3
    
    from subprocess import Popen, PIPE
    
    # cmd1 : dd if=/dev/zero bs=1m count=100
    # cmd2 : gzip
    # cmd3 : wc -c
    cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
    cmd2 = ['tee']
    cmd3 = ['wc', '-c']
    print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")
    
    p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
    p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
    p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)
    
    print("Output from last process : " + (p3.communicate()[0]).decode())
    
    # thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
    p1.wait()
    p2.wait()
    print("p1 return: ", p1.returncode)
    print("p2 return: ", p2.returncode)
    print("p3 return: ", p3.returncode)
    
    from subprocess import Popen, PIPE
    
    def string_to_2_procs_to_file(input_s, first_cmd, second_cmd, output_filename):
        with open(output_filename, 'wb') as out_f:
            p2 = Popen(second_cmd, stdin=PIPE, stdout=out_f)
            p1 = Popen(first_cmd, stdout=p2.stdin, stdin=PIPE)
            p1.communicate(input=bytes(input_s))
            p1.wait()
            p2.stdin.close()
            p2.wait()
    
    string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')