python - सबप्रोसेस में कई पाइप




pipe subprocess (2)

मैं सेलफ़िश का उपयोग करने की कोशिश कर रहा हूं, जो कई फास्टेक फ़ाइलों को तर्क के रूप में लेता है, एक रफ़स पाइपलाइन में। मैं अजगर में सबप्रोसेस मॉड्यूल का उपयोग करके सेलफिश को अंजाम देता हूं, लेकिन सबप्रोसेस कॉल में <() shell=True सेट करने पर भी काम नहीं करता है।

यह वह कमांड है जिसे मैं अजगर का उपयोग करके निष्पादित करना चाहता हूं:

sailfish quant [options] -1 <(cat sample1a.fastq sample1b.fastq) -2 <(cat sample2a.fastq sample2b.fastq) -o [output_file]

या (अधिमानतः):

sailfish quant [options] -1 <(gunzip sample1a.fastq.gz sample1b.fastq.gz) -2 <(gunzip sample2a.fastq.gz sample2b.fastq.gz) -o [output_file]

एक सामान्यीकरण:

someprogram <(someprocess) <(someprocess)

मैं अजगर में ऐसा करने के बारे में कैसे जाऊँगा? क्या उपप्रक्रम सही दृष्टिकोण है?


जब तक जेएफ सेबेस्टियन ने पाइप नाम का उपयोग करके एक उत्तर दिया है, अनाम पाइपों के साथ ऐसा करना संभव है।

import shlex
from subprocess import Popen, PIPE

inputcmd0 = "zcat hello.gz" # gzipped file containing "hello"
inputcmd1 = "zcat world.gz" # gzipped file containing "world"

def get_filename(file_):
    return "/dev/fd/{}".format(file_.fileno())

def get_stdout_fds(*processes):
    return tuple(p.stdout.fileno() for p in processes)

# setup producer processes
inputproc0 = Popen(shlex.split(inputcmd0), stdout=PIPE)
inputproc1 = Popen(shlex.split(inputcmd1), stdout=PIPE)

# setup consumer process
# pass input processes pipes by "filename" eg. /dev/fd/5
cmd = "cat {file0} {file1}".format(file0=get_filename(inputproc0.stdout), 
    file1=get_filename(inputproc1.stdout))
print("command is:", cmd)
# pass_fds argument tells Popen to let the child process inherit the pipe's fds
someprogram = Popen(shlex.split(cmd), stdout=PIPE, 
    pass_fds=get_stdout_fds(inputproc0, inputproc1))

output, error = someprogram.communicate()

for p in [inputproc0, inputproc1, someprogram]:
    p.wait()

assert output == b"hello\nworld\n"

बैश प्रक्रिया प्रतिस्थापन का अनुकरण करने के लिए:

#!/usr/bin/env python
from subprocess import check_call

check_call('someprogram <(someprocess) <(anotherprocess)',
           shell=True, executable='/bin/bash')

पायथन में, आप नामित पाइप का उपयोग कर सकते हैं:

#!/usr/bin/env python
from subprocess import Popen

with named_pipes(n=2) as paths:
    someprogram = Popen(['someprogram'] + paths)
    processes = []
    for path, command in zip(paths, ['someprocess', 'anotherprocess']):
        with open(path, 'wb', 0) as pipe:
            processes.append(Popen(command, stdout=pipe, close_fds=True))
    for p in [someprogram] + processes:
        p.wait()

जहां named_pipes(n) है:

import os
import shutil
import tempfile
from contextlib import contextmanager

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

बैश प्रक्रिया प्रतिस्थापन को लागू करने के लिए एक अन्य और अधिक बेहतर तरीका (डिस्क पर एक नामांकित प्रविष्टि बनाने की आवश्यकता नहीं) @Dunes द्वारा सुझाए गए अनुसार /dev/fd/N फ़ाइल नाम (यदि वे उपलब्ध हैं) का उपयोग करना है । FreeBSD पर, fdescfs(5) ( /dev/fd/# ) प्रक्रिया द्वारा खोले गए सभी फ़ाइल विवरणों के लिए प्रविष्टियाँ बनाता है । उपलब्धता का परीक्षण करने के लिए, दौड़ें:

$ test -r /dev/fd/3 3</dev/null && echo /dev/fd is available

यदि यह विफल रहता है; खरीद proc(5) लिए सीलिंक /dev/fd कोशिश करें क्योंकि यह कुछ लिनक्स पर किया जाता है:

$ ln -s /proc/self/fd /dev/fd

यहाँ /dev/fd someprogram <(someprocess) <(anotherprocess) कार्यान्वयन का कार्यान्वयन someprogram <(someprocess) <(anotherprocess) bash कमांड:

#!/usr/bin/env python3
from contextlib import ExitStack
from subprocess import CalledProcessError, Popen, PIPE

def kill(process):
    if process.poll() is None: # still running
        process.kill()

with ExitStack() as stack: # for proper cleanup
    processes = []
    for command in [['someprocess'], ['anotherprocess']]:  # start child processes
        processes.append(stack.enter_context(Popen(command, stdout=PIPE)))
        stack.callback(kill, processes[-1]) # kill on someprogram exit

    fds = [p.stdout.fileno() for p in processes]
    someprogram = stack.enter_context(
        Popen(['someprogram'] + ['/dev/fd/%d' % fd for fd in fds], pass_fds=fds))
    for p in processes: # close pipes in the parent
        p.stdout.close()
# exit stack: wait for processes
if someprogram.returncode != 0: # errors shouldn't go unnoticed
   raise CalledProcessError(someprogram.returncode, someprogram.args)

नोट: मेरे उबंटू मशीन पर, pass_fds कोड Python pass_fds में ही काम करता है, जबकि pass_fds 3.2 के बाद pass_fds उपलब्ध है।





named-pipes