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