2015-12-25 12 views
7

Zusammenfassung: Ich möchte Python-Skripte schreiben, die sich wie Bash-Skripte in der Kommandozeile verhalten, aber dann würde ich sie auch gerne zusammenstellen leicht in Python. Wo ich Probleme habe, ist der Leim, um das Letztere geschehen zu lassen.Wie man Python-Skripte pipe-fähig machen kann in bash und in python

So vorstellen, schrieb ich zwei Skripte, script1.py und script2.py und ich kann Rohr sie zusammen etwa so:

echo input_string | ./script1.py -a -b | ./script2.py -c -d 

Wie bekomme ich dieses Verhalten aus einer anderen Python-Datei? Hier ist der Weg, ich weiß, aber ich mag es nicht:

arg_string_1 = convert_to_args(param_1, param_2) 
arg_string_2 = convert_to_args(param_3, param_4) 
output_string = subprocess.check_output("echo " + input_string + " | ./script1.py " + arg_string_1 + " | ./script2.py " + arg_string_2) 

Wenn ich nicht die Vorteile von Multi-Threading nehmen wollte, habe ich so etwas tun könnte (?):

input1 = StringIO(input_string) 
output1 = StringIO() 
script1.main(param_1, param_2, input1, output1) 
input2 = StringIO(output1.get_value()) 
output2 = StringIO() 
script2.main(param_3, param_4, input2, output2) 

Hier ist der Ansatz, den ich versuchte, aber ich blieb stecken beim Schreiben des Klebers. Ich würde gerne lernen, wie ich meinen Ansatz unten fertigstellen kann, oder Vorschläge für ein besseres Design/Ansatz!

Mein Ansatz: Ich schrieb script1.py und script2.py aussehen:

#!/usr/bin/python3 

... # import sys and define "parse_args" 

def main(param_1, param_2, input, output): 
    for line in input: 
    ... 
    print(stuff, file=output) 

if __name__ == "__main__": 
    parameter_1, parameter_2 = parse_args(sys.argv) 
    main(parameter_1, parameter_2, sys.stdin, sys.stdout) 

Dann wollte ich so etwas schreiben, aber nicht wissen, wie zu beenden: Für die

pipe_out, pipe_in = ???? 
output = StringIO() 
thread_1 = Thread(target=script1.main, args=(param_1, param_2, StreamIO(input_string), pipe_out)) 
thread_2 = Thread(target=script2.main, args=(param_3, param_4, pipe_in, output) 
thread_1.start() 
thread_2.start() 
thread_1.join() 
thread_2.join() 
output_str = output.get_value() 

Antwort

1

"pipe in" verwendet sys.stdin mit der Methode readlines(). (Mit der Methode read() würde jeweils ein Zeichen gelesen.)

Zum Weiterleiten von Informationen von einem Thread zum anderen können Sie Queue verwenden. Sie müssen einen Weg definieren, um das Ende der Daten zu signalisieren. Da in meinem Beispiel alle zwischen Threads übergebenen Daten str sind, verwende ich einfach ein Objekt None, um das Ende der Daten zu signalisieren (da es in den übertragenen Daten nicht erscheinen kann).

Man könnte auch mehr Threads verwenden oder verschiedene Funktionen in Threads verwenden.

Ich habe die sys.argv in meinem Beispiel nicht enthalten, um es einfach zu halten. Es zu modifizieren, um Parameter (parameter1, ...) zu erhalten, sollte einfach sein.

import sys 
from threading import Thread 
from Queue import Queue 
import fileinput 

def stdin_to_queue(output_queue): 
    for inp_line in sys.stdin.readlines():  # input one line at at time             
    output_queue.put(inp_line, True, None) # blocking, no timeout 
    output_queue.put(None, True, None) # signal the end of data             


def main1(input_queue, output_queue, arg1, arg2): 
    do_loop = True 
    while do_loop: 
    inp_data = input_queue.get(True) 
    if inp_data is None: 
     do_loop = False 
     output_queue.put(None, True, None) # signal end of data              
    else: 
     out_data = arg1 + inp_data.strip('\r\n').upper() + arg2 # or whatever transformation...          
     output_queue.put(out_data, True, None) 

def queue_to_stdout(input_queue): 
    do_loop = True 
    while do_loop: 
    inp_data = input_queue.get(True) 
    if inp_data is None: 
     do_loop = False 
    else: 
     sys.stdout.write(inp_data) 


def main(): 
    q12 = Queue() 
    q23 = Queue() 
    q34 = Queue() 
    t1 = Thread(target=stdin_to_queue, args=(q12,)) 
    t2 = Thread(target=main1, args=(q12,q23,'(',')')) 
    t3 = Thread(target=main1, args=(q23,q34,'[',']')) 
    t4 = Thread(target=queue_to_stdout, args=(q34,)) 
    t1.start() 
    t2.start() 
    t3.start() 
    t4.start() 


main() 

Schließlich testete ich dieses Programm (python2) mit einer Textdatei.

head sometextfile.txt | python script.py 
+0

Danke, das scheint gut, nur eine Menge Code. Ich hatte gehofft, dass es etwas Konkretes geben könnte. Aber wenn die Leistung gut ist, könnte es sich lohnen. – usul

+0

Es gibt viele Möglichkeiten, es zu reduzieren. Beispielsweise könnten Sie direkt aus einer Datei lesen und direkt in eine Datei innerhalb desselben Threads schreiben. Ich habe absichtlich viele Dinge getrennt, um viele Dinge einzeln zu illustrieren. –

1

Umleiten der Rückgabewert auf stdout je nachdem, ob das Skript von der Kommandozeile ausgeführt wird:

#!/usr/bin/python3 
import sys 

# Example function 
def main(input): 
    # Do something with input producing stuff 
    ... 
    return multipipe(stuff) 

if __name__ == '__main__': 
    def multipipe(data): 
     print(data) 

    input = parse_args(sys.argv) 
    main(input) 
else: 
    def multipipe(data): 
     return data 

Jedes andere Skript die gleichen zwei Definitionen von multipipe haben. Verwenden Sie jetzt multipipe für die Ausgabe.

Wenn Sie die Skripts alle rufen zusammen aus der Befehlszeile $ ./scrip1.py | ./scrip2.py, jeder wird __name__ == '__main__' haben und so multipipe druckt es durch das nächste Skript als Argument gelesen werden nach stdout (und None zurückkehren, so dass jede Funktion gibt None , aber in diesem Fall sehen Sie sowieso nicht die Rückgabewerte).

Wenn Sie sie in einem anderen Python-Skript aufrufen, gibt jede Funktion das zurück, was Sie an multipipe übergeben haben.

Effektiv können Sie Ihre vorhandenen Funktionen verwenden, ersetzen Sie einfach print(stuff, file=output) durch return multipipe(stuff). Schön und einfach.

Um es mit Multithreading oder Multiprocessing zu verwenden, stellen Sie die Funktionen so ein, dass jede Funktion eine einzige Sache zurückgibt, und stecken Sie sie in eine einfache Funktion, die Daten zu einer Multithreading-Warteschlange hinzufügt. Ein Beispiel für ein solches Warteschlangensystem finden Sie unter the sample at the bottom of the Queue docs. Bei diesem Beispiel stellen Sie einfach sicher, dass jeder Schritt in der Pipeline None (oder einen anderen Sentinel-Wert Ihrer Wahl - ich mag ... dafür, da es sehr selten ist, dass Sie das Ellipsis Objekt aus irgendeinem anderen Grund als als ein Marker für seine Singleton-Ness) in der Warteschlange zum nächsten, um das Done-ness zu signalisieren.

0

Es gibt eine sehr einfache Lösung mit der Standardklasse Popen.

Hier ist ein Beispiel:

#this is the master python program 
import subprocess 
import sys 
import os 

#note the use of stdin and stdout arguments here 
process1 = subprocess.Popen(['./script1.py'], stdin=sys.stdin, stdout=subprocess.PIPE) 
process2 = subprocess.Popen(['./script2.py'], stdin=process1.stdout) 

process1.wait() 
process2.wait() 

die beiden Skripte sind:

#!/usr/bin/env python 
#script1.py 
import sys 

for line in sys.stdin: 
    print(line.strip().upper()) 

Hier ist die zweite

#!/usr/bin/env python 
#script2.py 
import sys 

for line in sys.stdin: 
    print("<{}>".format(line.strip())) 
+0

Danke, das ist definitiv besser als meine Lösung. Es bleibt offen, wie mit Parametern umzugehen ist. Diese Lösung scheint es erforderlich zu machen, ein Parameterobjekt zu nehmen, es in eine Zeichenkette umzuwandeln, es durch Popen zu führen, und dann verarbeitet process1 die Zeichenkette und erzeugt das Objekt neu. Es wäre schön, Objekte einfach direkt durchzulassen. – usul

+0

@usul Parameter sind kein Problem, einfach '['./script1.py', 'param1', 'param2']' in den Aufruf von Popen statt nur '['./script1.py']' –

+0

Hi @Yoav, ja das funktioniert gut, wenn die Parameter bereits Strings sind. Aber nicht so schön, wenn es sich um Listen oder kompliziertere Objekte handelt. Zum Beispiel, wenn ich ein Datetime-Objekt habe, muss ich es zuerst in eine Zeichenkette konvertieren und dann übergeben, dann muss script1.py es wieder in ein Datetime-Objekt analysieren. – usul