允许对python子流程进行多个输入

允许对python子流程进行多个输入,python,pandas,subprocess,Python,Pandas,Subprocess,我有一个与几年前一个问题几乎相同的问题:它只得到一个答案,但没有实现。我希望这篇报道能帮助我和其他人澄清问题 如上所述,我想使用subprocess包装一个接受多个输入的命令行工具。特别是,我希望避免将输入文件写入磁盘,但更愿意使用命名管道,如上文所述。这应该是“学习如何”,因为我承认我以前从未尝试过使用命名管道。我将进一步说明,我目前拥有的输入是两个数据帧,我希望将其中一个作为输出返回 通用命令行实现: /usr/local/bin/my_command inputfileA.csv inpu

我有一个与几年前一个问题几乎相同的问题:它只得到一个答案,但没有实现。我希望这篇报道能帮助我和其他人澄清问题

如上所述,我想使用subprocess包装一个接受多个输入的命令行工具。特别是,我希望避免将输入文件写入磁盘,但更愿意使用命名管道,如上文所述。这应该是“学习如何”,因为我承认我以前从未尝试过使用命名管道。我将进一步说明,我目前拥有的输入是两个数据帧,我希望将其中一个作为输出返回

通用命令行实现:

/usr/local/bin/my_command inputfileA.csv inputfileB.csv -o outputfile
可以预见,我当前的实现不起作用。我不知道数据帧是如何/何时通过命名管道发送到命令进程的,我非常感谢您的帮助

import os
import StringIO
import subprocess
import pandas as pd
dfA = pd.DataFrame([[1,2,3],[3,4,5]], columns=["A","B","C"])
dfB = pd.DataFrame([[5,6,7],[6,7,8]], columns=["A","B","C"]) 

# make two FIFOs to host the dataframes
fnA = 'inputA'; os.mkfifo(fnA); ffA = open(fnA,"w")
fnB = 'inputB'; os.mkfifo(fnB); ffB = open(fnB,"w")

# don't know if I need to make two subprocesses to pipe inputs 
ppA  = subprocess.Popen("echo", 
                    stdin =subprocess.PIPE,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE)
ppB  = subprocess.Popen("echo", 
                    stdin = suprocess.PIPE,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE)

ppA.communicate(input = dfA.to_csv(header=False,index=False,sep="\t"))
ppB.communicate(input = dfB.to_csv(header=False,index=False,sep="\t"))


pope = subprocess.Popen(["/usr/local/bin/my_command",
                        fnA,fnB,"stdout"],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)
(out,err) = pope.communicate()

try:
    out = pd.read_csv(StringIO.StringIO(out), header=None,sep="\t")
except ValueError: # fail
    out = ""
    print("\n###command failed###\n")

os.unlink(fnA); os.remove(fnA)
os.unlink(fnB); os.remove(fnB)

所以有几件事可能会把你搞砸。上一篇文章中重要的一点是将这些FIFO想象成普通文件。除了正常情况外,如果您试图在一个进程中读取管道,而没有连接另一个进程在另一端写入管道,则会发生阻塞(反之亦然)。这就是我处理这种情况的方式,我会尽力描述我的想法


首先,当您在主进程中,尝试调用
ffA=open(fnA,'w')
时,您遇到了我上面提到的问题——管道的另一端还没有人从中读取数据,因此在发出命令后,主进程将阻塞。为此,您可能需要更改代码以删除
open()
调用:

# make two FIFOs to host the dataframes
fnA = './inputA';
os.mkfifo(fnA);
fnB = './inputB';
os.mkfifo(fnB);
好的,我们已经准备好了管道“inputA”和“inputB”,准备好打开读/写。为了防止像上面那样发生阻塞,我们需要启动两个子进程来调用
open()
。由于我对子流程库不是特别熟悉,所以我将只讨论几个子流程

for x in xrange(2):

    pid = os.fork()
    if pid == 0:
            if x == 0:
                    dfA.to_csv(open(fnA, 'w'), header=False, index=False, sep='\t')
            else:
                    dfB.to_csv(open(fnB, 'w'), header=False, index=False, sep='\t')
            exit()
    else:
            continue
好的,现在我们让这两个子进程在等待写入各自的FIFO时阻塞。现在,我们可以运行命令连接到管道的另一端并开始读取

pope = subprocess.Popen(["./my_cmd.sh",
                        fnA,fnB],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)
(out,err) = pope.communicate()

try:
    out = pd.read_csv(StringIO.StringIO(out), header=None,sep="\t")
except ValueError: # fail
    out = ""
    print("\n###command failed###\n")
我发现的最后一点是,取消管道链接似乎会将其删除,因此无需调用
remove()

在我的机器上,打印语句生成:

out:     0  1  2
0  1  2  3
1  3  4  5
2  5  6  7
3  6  7  8
顺便说一下,我的命令只是几个cat语句:

#!/bin/bash

cat $1
cat $2

在不将数据写入磁盘的情况下,将数据传递给子进程不需要其他进程:

#!/usr/bin/env python
import os
import shutil
import subprocess
import tempfile
import threading
from contextlib import contextmanager    
import pandas as pd

@contextmanager
def named_pipes(count):
    dirname = tempfile.mkdtemp()
    try:
        paths = []
        for i in range(count):
            paths.append(os.path.join(dirname, 'named_pipe' + str(i)))
            os.mkfifo(paths[-1])
        yield paths
    finally:
        shutil.rmtree(dirname)

def write_command_input(df, path):
    df.to_csv(path, header=False,index=False, sep="\t")

dfA = pd.DataFrame([[1,2,3],[3,4,5]], columns=["A","B","C"])
dfB = pd.DataFrame([[5,6,7],[6,7,8]], columns=["A","B","C"])

with named_pipes(2) as paths:
    p = subprocess.Popen(["cat"] + paths, stdout=subprocess.PIPE)
    with p.stdout:
        for df, path in zip([dfA, dfB], paths):
            t = threading.Thread(target=write_command_input, args=[df, path]) 
            t.daemon = True
            t.start()
        result = pd.read_csv(p.stdout, header=None, sep="\t")
p.wait()

cat
用于演示。您应该改用您的命令(
“/usr/local/bin/my_command”
)。我假设您不能使用标准输入传递数据,而必须通过文件传递输入。从子流程的标准输出读取结果。

谢谢NBartley。我用一个直接的“cat”命令代替您包含的shell脚本重新创建了这个脚本。我对os.fork不够熟悉,无法理解exit()函数的存在——当我实现它时,每次都会引发一个SystemExit。这是我的预期行为,还是我的输入错误?嗯,代码在其他方面有效吗?如果不是的话,我会想象一个输入错误。但这可能是一个意想不到的系统差异——我在Ubuntu机器上使用的是Python 2.7.3。您可以尝试的另一种方法是将exit()替换为os.\u exit(os.EX\u OK)。我的设置与之类似(ubuntu 14.10、python 2.7.8),因此我认为不太可能出现系统性差异,但这正是我最终使用的修复(os.\u exit(1)),并且它在不引发任何代码的情况下工作得很好。再次感谢您的帮助。我已经实现并测试了这两个答案,在可能的情况下使用了共享代码,并且可以确认这两个答案都按预期工作。我之所以选择J.F.Sebastian的实现作为选择的答案,主要是因为它在我的系统上进行测试时(使用“cat”示例)效率略高一些(大约快1.2倍)。这就是说,应该指出的是,NBartley的答案在精神和语法上可能更接近我发布的代码,因此任何低效都可能是OP自己的!谢谢JF。实现非常清楚,我不会想到使用线程模块而不是子进程来传递数据。我不确定我是否理解您的评论,即我不需要额外的进程来传递数据-此解决方案是否本质上通过线程模块创建子进程,作为子进程的替代方案?@blackgore:1<代码>线程化不会创建新进程2<代码>线程化用于执行async.io,但不是必需的;您可以使用select循环
#!/usr/bin/env python
import os
import shutil
import subprocess
import tempfile
import threading
from contextlib import contextmanager    
import pandas as pd

@contextmanager
def named_pipes(count):
    dirname = tempfile.mkdtemp()
    try:
        paths = []
        for i in range(count):
            paths.append(os.path.join(dirname, 'named_pipe' + str(i)))
            os.mkfifo(paths[-1])
        yield paths
    finally:
        shutil.rmtree(dirname)

def write_command_input(df, path):
    df.to_csv(path, header=False,index=False, sep="\t")

dfA = pd.DataFrame([[1,2,3],[3,4,5]], columns=["A","B","C"])
dfB = pd.DataFrame([[5,6,7],[6,7,8]], columns=["A","B","C"])

with named_pipes(2) as paths:
    p = subprocess.Popen(["cat"] + paths, stdout=subprocess.PIPE)
    with p.stdout:
        for df, path in zip([dfA, dfB], paths):
            t = threading.Thread(target=write_command_input, args=[df, path]) 
            t.daemon = True
            t.start()
        result = pd.read_csv(p.stdout, header=None, sep="\t")
p.wait()