3 Stimmen

Wie kann ich Python verwenden, um stdin/stdout an ein Perl-Skript weiterzuleiten?

Dieser Python-Code leitet die Daten problemlos durch das Perl-Skript.

import subprocess
kw = {}
kw['executable'] = None
kw['shell'] = True
kw['stdin'] = None
kw['stdout'] = subprocess.PIPE
kw['stderr'] = subprocess.PIPE
args = ' '.join(['/usr/bin/perl','-w','/path/script.perl','<','/path/mydata'])
subproc = subprocess.Popen(args,**kw)
for line in iter(subproc.stdout.readline, ''):
    print line.rstrip().decode('UTF-8')

Dazu muss ich jedoch zunächst meine Puffer in einer Datei auf der Festplatte speichern (/path/mydata). Sauberer ist es, die Daten in Python-Code in einer Schleife durchzugehen und Zeile für Zeile an den Unterprozess zu übergeben, etwa so:

import subprocess
kw = {}
kw['executable'] = '/usr/bin/perl'
kw['shell'] = False
kw['stderr'] = subprocess.PIPE
kw['stdin'] = subprocess.PIPE
kw['stdout'] = subprocess.PIPE
args = ['-w','/path/script.perl',]
subproc = subprocess.Popen(args,**kw)
f = codecs.open('/path/mydata','r','UTF-8')
for line in f:
    subproc.stdin.write('%s\n'%(line.strip().encode('UTF-8')))
    print line.strip()  ### code hangs after printing this ###
    for line in iter(subproc.stdout.readline, ''):
        print line.rstrip().decode('UTF-8')
subproc.terminate()
f.close()

Der Code bleibt mit der Lesezeile nach dem Senden der ersten Zeile an den Unterprozess hängen. Ich habe andere ausführbare Dateien, die genau den gleichen Code perfekt verwenden.

Meine Datendateien können recht groß sein (1,5 GB). Gibt es eine Möglichkeit, die Daten ohne Speichern in eine Datei zu leiten? Ich möchte das Perl-Skript nicht neu schreiben, damit es mit anderen Systemen kompatibel ist.

1voto

srgerg Punkte 17989

Ihr Code ist in der Zeile blockiert:

for line in iter(subproc.stdout.readline, ''):

denn die einzige Möglichkeit, diese Iteration zu beenden, ist das Erreichen von EOF (end-of-file), was durch die Beendigung des Unterprozesses geschieht. Sie wollen jedoch nicht warten, bis der Prozess beendet ist, sondern nur, bis er die Verarbeitung der an ihn gesendeten Zeile abgeschlossen hat.

Darüber hinaus haben Sie Probleme mit der Pufferung, wie Chris Morgan bereits erwähnt hat. Eine andere Frage auf Stackoverflow erörtert, wie man mit einem Unterprozess nicht blockierende Lesevorgänge durchführen kann. Ich habe eine schnelle und schmutzige Anpassung des Codes aus dieser Frage auf Ihr Problem gehackt:

def enqueue_output(out, queue):
    for line in iter(out.readline, ''):
        queue.put(line)
    out.close()

kw = {}
kw['executable'] = '/usr/bin/perl'
kw['shell'] = False
kw['stderr'] = subprocess.PIPE
kw['stdin'] = subprocess.PIPE
kw['stdout'] = subprocess.PIPE
args = ['-w','/path/script.perl',]
subproc = subprocess.Popen(args, **kw)
f = codecs.open('/path/mydata','r','UTF-8')
q = Queue.Queue()
t = threading.Thread(target = enqueue_output, args = (subproc.stdout, q))
t.daemon = True
t.start()
for line in f:
    subproc.stdin.write('%s\n'%(line.strip().encode('UTF-8')))
    print "Sent:", line.strip()  ### code hangs after printing this ###
    try:
        line = q.get_nowait()
    except Queue.Empty:
        pass
    else:
        print "Received:", line.rstrip().decode('UTF-8')

subproc.terminate()
f.close()

Es ist sehr wahrscheinlich, dass Sie Änderungen an diesem Code vornehmen müssen, aber zumindest wird er nicht blockiert.

1voto

tahoar Punkte 1758

Danke srgerg. Ich hatte auch die Lösung mit dem Einfädeln versucht. Diese Lösung allein blieb aber immer hängen. Sowohl in meinem vorherigen Code als auch in srgergs Code fehlte die endgültige Lösung, Dein Tipp hat mich auf eine letzte Idee gebracht.

Die endgültige Lösung schreibt genügend Dummy-Daten, um die letzten gültigen Zeilen aus dem Puffer zu erzwingen. Um dies zu unterstützen, habe ich Code hinzugefügt, der verfolgt, wie viele gültige Zeilen in stdin geschrieben wurden. Die mit einem Thread versehene Schleife öffnet die Ausgabedatei, speichert die Daten und bricht ab, wenn die gelesenen Zeilen den gültigen Eingabezeilen entsprechen. Diese Lösung stellt sicher, dass die Datei bei jeder Größe zeilenweise gelesen und geschrieben wird.

def std_output(stdout,outfile=''):
    out = 0
    f = codecs.open(outfile,'w','UTF-8')
    for line in iter(stdout.readline, ''):
        f.write('%s\n'%(line.rstrip().decode('UTF-8')))
        out += 1
        if i == out: break
    stdout.close()
    f.close()

outfile = '/path/myout'
infile = '/path/mydata'

subproc = subprocess.Popen(args,**kw)
t = threading.Thread(target=std_output,args=[subproc.stdout,outfile])
t.daemon = True
t.start()

i = 0
f = codecs.open(infile,'r','UTF-8')
for line in f:
    subproc.stdin.write('%s\n'%(line.strip().encode('UTF-8')))
    i += 1
subproc.stdin.write('%s\n'%(' '*4096)) ### push dummy data ###
f.close()
t.join()
subproc.terminate()

0voto

Chris Morgan Punkte 78929

Siehe die im Handbuch erwähnten Warnungen zur Verwendung von Popen.stdin et Popen.stdout (knapp über Popen.stdin ):

Warnung: Verwenden Sie communicate() statt .stdin.write , .stdout.read ou .stderr.read um Deadlocks zu vermeiden, die dadurch entstehen, dass einer der anderen OS-Pipe-Puffer voll ist und den Kindprozess blockiert.

Mir ist klar, dass es nicht sehr wünschenswert ist, einen String von anderthalb Gigabyte auf einmal im Speicher zu haben, aber mit communicate() ist ein Weg, der 意志 arbeiten, während, wie Sie beobachtet haben, sobald der OS-Pipe-Puffer voll ist, die stdin.write() + stdout.read() Weg in eine Sackgasse geraten kann.

Wird mit communicate() für Sie machbar?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X