允许多个输入到python子进程

时间:2015-07-23 14:07:59

标签: python pandas subprocess multiple-files

我和几年前提出的问题几乎完全相同:Python subprocess with two inputs收到一个答案但没有实施。我希望这个转贴可以帮助我和其他人清理事情。

如上所述,我想使用subprocess来包装一个需要多个输入的命令行工具。特别是,我想避免将输入文件写入磁盘,但宁愿使用例如命名管道,如上所述。这应该是“学习如何”,因为我承认我之前从未尝试使用命名管道。我将进一步说明我所拥有的输入目前是两个pandas数据帧,我想把它作为输出返回。

通用命令行实现:

/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)

2 个答案:

答案 0 :(得分: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")。我假设你不能使用标准输入传递数据,你必须通过文件传递输入。结果从子进程中读取'标准输出。

答案 1 :(得分:1)

所以有一些事情可能会让你感到困惑。上一篇文章中重要的一点是将这些FIFO视为普通文件。除了发生的正常事情是,如果你试图在一个进程中读取管道而不挂钩另一个进程在另一端写入它,它们会阻塞(反之亦然)。这就是我如何应对这种情况,我会尽力描述我的想法。

首先,当您在主要流程中,并且您尝试致电ffA = open(fnA, 'w')时,您会遇到我上面谈到的问题 - 在另一端没有人管道从中读取数据,因此在发出命令后,主进程才会阻塞。为了解决这个问题,您可能需要更改代码以删除open()来电:

# make two FIFOs to host the dataframes
fnA = './inputA';
os.mkfifo(fnA);
fnB = './inputB';
os.mkfifo(fnB);

好的,我们有管道输入A'和'输入B'制作并准备开放阅读/写作。为了防止阻塞发生,如上所述,我们需要启动几个子进程来调用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()

os.unlink(fnA); 
os.unlink(fnB);
print "out: ", out

在我的机器上,print语句产生:

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

#!/bin/bash

cat $1
cat $2