subprocess.popen -> Ausgabe anzeigen und weiterverarbeiten

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo zusammen!
Ich kämpfe im Moment grade mit "subprocess.popen". Hintergrund ist, dass ich aus Python heraus eine Batch-Datei (Windows) aufrufen will, dieser Batchdatei dann per Python 2 Parameter mitgebe und gerne die "Ausgabe" dieser Aktion geliefert bekommen würde.

Nur um zu testen, ob mein "Programm" überhaupt läuft habe ich mal das hier versucht:

Code: Alles auswählen

import subprocess
befehl = 'cmd'
ausgabe = subprocess.Popen(befehl)
exit
Gut, es wird eine Dos-Box geöffnet und bleibt offen (soweit klar).

Wenn ich das Ganze aber nun mit einer Batch Datei mache, zuckt nur kurz das "Dosfenster" auf (ich nehme an, dass die Batchdatei einfach ruck zuck abgearbeitet wird) und das war es. Es erfolgt also keine Ausgabe auf den Bildschirm.

Nun dachte ich, ich könnte das Ganze mittels "STDOUT" ausgeben. Dem ist aber nicht so.

Meine Fragen dazu:
1.) Wie kann ich anzeigen (ausgeben) lassen, was die Batch macht?
2.) Wie könnte man die Ausgabe direkt in Python weiter verarbeiten?

LG und vielen Dank!

Daniel
Zuletzt geändert von mcdaniels am Montag 26. März 2012, 10:45, insgesamt 1-mal geändert.
BlackJack

@mcdaniels: Das was Du `ausgabe` nennst ist ein Objekt was den Prozess darstellt und über das man mit dem Prozess kommunizieren kann. Die Dokumentation enthält einige Beispiele zur Umleitung von Ein- und Ausgaben.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

@Blackjack: Danke, da wären wir wieder bei meiner damaligen "Objektsache" :-) Ich schau mir die Doku nochmal an. Ausserdem bin ich auch hier noch am lesen: http://docs.python.org/py3k/library/
Nicht ganz so einfach, wenn man leider (wiedermal) lange Zeit nichts mit Python gemacht hat.

Edit: Die Methode .check_output scheint mir hier ein geeigneter Kandidat zu sein, um den Output "abzugreifen". Edit2: (doch nicht wirklich)
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Hier ist ein Beispiel.

Code: Alles auswählen

from subprocess import Popen

comm = subprocess.Popen(['ipconfig'], stdout=subprocess.PIPE)
data = comm.communicate()
print data
Dokumentation zu subprocess.Popen: http://docs.python.org/library/subproce ... en-objects. Unter Python 3 hat sich da IMHO nichts geändert.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Servus!
Danke dir, ein Beispiel ist immer gut.

Läuft bei mir nur in der Form (wobei print() klar ist)

Code: Alles auswählen

import subprocess
comm = subprocess.Popen(['ipconfig'], stdout=subprocess.PIPE)
data = comm.communicate()
print (data)
Zuletzt geändert von mcdaniels am Montag 26. März 2012, 10:46, insgesamt 1-mal geändert.
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Das war ein Fehler in meinem Beispiel. Beim Copy, Paste & Delete habe ich die verkehrte Zeile gelöscht.

Du kannst je nach Geschmack eine der beiden folgenden Varianten verwenden:

Code: Alles auswählen

import subprocess
comm = subprocess.Popen(['ipconfig'], stdout=subprocess.PIPE)

Code: Alles auswählen

from subprocess import Popen
comm = Popen(['ipconfig'], stdout=subprocess.PIPE)
Ich bevorzuge meistens die erste Variante.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Danke!
Mein "Projekt" nimmt ungeahnte Dimensionen an :D

Ziel ist es, dass *.pdf Dateien aus einem Verzeichnis eingelesen werden (Liste). Danach soll mittels einem PDF Signer (Portable Signer) die PDF Datei signiert werden. Mein PDF-einlesen Code sieht so aus:

Code: Alles auswählen

import glob
pdf_dateien = glob.glob('*.pdf')
anzahl_dateien = len(pdf_dateien)

for zaehler in range (-1,int(anzahl_dateien-1)):
                     print(pdf_dateien[zaehler])
                     signieren = pdf_dateien[zaehler] + ' -o ' + pdf_dateien[zaehler] + '_sig.pdf' + ' -s acert-pkcs12.p12 -p passwort'
                     print (signieren)

Statt der Printfunktion muss ich dann hier auf subprocess.Popen zurückgreifen, das eine Bat aufruft und dem Portable Signer die Parameter übergibt. Bin mal gespannt, ob ich das zusammen bringe. Vor allem frage ich mich grade, ob mir das Programm beim Durchgehen der gefundenen PDF Dateien immer schön brav wartet bis der jeweilige Signierungsvorgang pro Datei fertig ist, oder ob x-fache Signierungsvorgänge gestartet werden. (sonst eventuell ein Fall für popen.wait...)

Dass ich beim Vergeben des 2ten Namens (mit der Endung _sig.pdf) ein Problem habe, weiß ich.

EDIT: Wie befürchtet klappt die Parameterübergabe nicht. Hatte mir das auch zu einfach vorgestellt. Dachte es klappt so:

Code: Alles auswählen

comm = subprocess.Popen(['signpython.bat' + signieren], stdout=subprocess.PIPE)
:)

Muss wohl auch mit stdin arbeiten?

Naja wenn ich das Ganze mit

Code: Alles auswählen

os.system(signieren)
mache und in die Variable signieren den gesamten Batchcode inkl. die Variablen reinpacke läuft es. Ist aber eigentlich nicht das was ich wollte, da man ja eher nicht auf os.system zurückgreifen soll und hier eigentlich subprocess angesagt wäre.

LG
Daniel
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

mcdaniels hat geschrieben:

Code: Alles auswählen

import glob
pdf_dateien = glob.glob('*.pdf')
anzahl_dateien = len(pdf_dateien)

for zaehler in range (-1,int(anzahl_dateien-1)):
                     print(pdf_dateien[zaehler])
                     signieren = pdf_dateien[zaehler] + ' -o ' + pdf_dateien[zaehler] + '_sig.pdf' + ' -s acert-pkcs12.p12 -p passwort'
                     print (signieren)
Das ist kein idiomatisches Python. Schleifen schreibt man in Python praktischerweise so, dass direkt über die Liste iteriert wird.

Code: Alles auswählen

data = ['file1.pdf', 'file2.pdf', 'filen.pdf']

# unübersichtlich
for i in range(len(data)):
    print(data[i])

# übersichtlicher und schneller als die andere Version
for filename in data:
    print(filename)
Die Übergabe von Parametern an Popen erfolgt dann als Liste, nicht als ein langer String.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo /me - Danke für deine Geduld.

Momentan sieht das Ganze so aus:

Code: Alles auswählen

import glob
import os

def start_sig():
    pdf_dateien = glob.glob('*.pdf')
    for filename in pdf_dateien:
        print('Signiere: ' + str(filename))
        signieren = 'java -jar PortableSigner.jar -b de -c "Dieses Dokument wurde amtssigniert. Informationen zur Pruefung finden Sie unter http://www.meineseite.at " -i Bildmarke.jpg -n -t ' + '"' + str(filename) + '"' + ' -o ''' + '"' + str(filename).rstrip('.pdf') + '_sig.pdf"' + ' -s acert.p12 -p Passwort'
        os.system(signieren)


def verschieben_sig():
    try:
        kopieren = 'copy *_sig.pdf sig-pdf'
        loeschen = 'del *_sig.pdf'
        os.system(kopieren)
        os.system(loeschen)
    except:
        print('Es ist ein Fehler aufgetreten')
Wahrscheinlich schlagt man als Profi die Hände über dem Kopf zusammen. Greift bei meiner Funktion verschieben_sig() der try except block eigentlich, wenn ein Fehler auftritt. Ich habe nicht den Eindruck.

Das mit dem Anhängen als Liste bei popen habe ich noch nicht wirklich verstanden. Wird dann hier ein Listeintrag pro Parameter verwendet:

Bsp:

Code: Alles auswählen

['-i Bildmarke.jpg', '-n','-t','str(filename)']


LG
Daniel
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo nochmals!
Habe es nun so versucht:

Code: Alles auswählen

comm = subprocess.Popen(['signpython.bat', str(filename),' -o ', str(filename).rstrip('.pdf') + '-sig.pdf','-s acert.p12 -p Passwort'])
Es wird aber nur die .bat ausgeführt. Die Parameter werden nicht angehängt.

Wäre dankbar, falls ihr Tips für mich hättet.

LG
Daniel
BlackJack

@mcdaniels: Die Parameter werden sehr wohl angehängt. Die Liste muss den Inhalt haben, den das aufgerufene Programm als Argumentliste erwartet. Also das was in Python in `sys.argv` ankommen würde. Das aufgerufene Programm wird weder mit ' -o ' noch mit '-s acert.p12 -p Passwort' etwas anfangen können. Das sind beides keine Elemente die es erwartet. Es sucht nach Elementen wie '-o', '-s' und '-p' und erwartet danach jeweils ein Element mit dem Wert für die Option.

`rstrip()` tut nicht das was Du denkst was es tut:

Code: Alles auswählen

In [95]: 'proof.pdf'.rstrip('.pdf')
Out[95]: 'proo'

In [96]: 'sepp.pdf'.rstrip('.pdf')
Out[96]: 'se'
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo Blackjack!

Stimmt, das hatte ich nicht bedacht, was rstrip angeht. Danke für den Hinweis. Könntest du eventuell das mit den Argumenten noch präzisieren?

Meinst du ich muss java -jar PortableSigner.jar direkt aus Python heraus aufrufen, weil die .BAT diese Parameter nicht erwartet sondern der PortableSigner.jar?

LG
Daniel
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Evtl macht es sogar Sinn, für deine Abfrage Jython zu nutzen? Damit kannst du Java-Bibliotheken innerhalb von Python importieren und dementsprechend direkt auf deren Klassen zugreifen - sofern eine Installation auf dem System (oder den Systemen), wo dein Skript zum Einsatz kommt, denn möglich ist.

Ansonsten gibt es halt `subprocess.check_output()`. Dort musst du die Argumente zum Aufruf als Liste übergeben (kennst du ja jetzt schon) und erhälst die Programmausgabe als String zurück. Bei einem Fehler im benutzten Programm (resp. Errorcode des Programms != 0) wird automatisch eine Exception von Python geworfen.

Und ja, wenn die Batchdatei im Prinzip nur die Zeile für den Programmaufruf beinhaltet, dann kannst du das natürlich auch direkt über Python ausführen lassen, ohne den Umweg zu gehen. An der Batchdatei selbst dürfte sich ja vermutlich so schnell nichts ändern.

Und sofern es dir zu umständlich ist, dein Kommando in einzelne Häppchen zu zerstückeln, bietet sich shlex.split(dein_kommando) an.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

mcdaniels hat geschrieben:Ziel ist es, dass *.pdf Dateien aus einem Verzeichnis eingelesen werden (Liste). Danach soll mittels einem PDF Signer (Portable Signer) die PDF Datei signiert werden.
Passiert denn noch irgendwas darüber hinaus? Also sozusagen etwas pythonspezifisches? Bisher sieht dein Code nämlich so aus, als ahmst du Shellprogrammierung mit Python nach. Nicht mal das: Du rufst Shellprogramme (wie `copy` und `del`) über Python auf, wo Python für Dateioperationen usw reichlich Funktionalität mitbringt und dafür nicht auf externe Programme angewiesen ist. Da möchte man in der Tat die Hände über den Kopf zusammenschlagen.

Bist du denn sicher, dass deine Aufgabenstellung nicht besser als reine Batchdatei gelöst ist? Denn nur weil man Python auch dafür benutzen *kann*, muss man es ja nicht zwangsläufig aucht tun, wenn's die Sache eigentlich viel umständlicher macht. ;)
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

mcdaniels hat geschrieben:Stimmt, das hatte ich nicht bedacht, was rstrip angeht. Danke für den Hinweis. Könntest du eventuell das mit den Argumenten noch präzisieren?
Ein Punkt ist: '-o' ist nicht gleich ' -o '.
BlackJack

@mcdaniels: Zur Präzisierung mit den Argumenten: Du musst eine Liste übergeben welche die Argumente einzeln enthält, so wie sie eine Shell dem Programm beim Aufruf übergeben würde. Und '-s acert.p12 -p Passwort' ist nicht *ein* Argument sondern *vier*. Die Shell teilt so etwas in der Regel an Leerzeichen auf wenn man es nicht „schützt”, zum Beispiel in dem man ein Argument in Anführungszeichen einfasst oder einzelne Leerzeichen mit einem '\' „escape”t.

Der Vorteil und damit der Grund warum man bei `Popen` diese Liste übergibt, liegt darin, dass jedes Element so übergeben wird, wie es in den einzelnen Elementen angegeben ist. Ohne das eine Shell da noch einmal drüber geht und bestimmte Zeichen besonders interpretiert. Im Umkehrschluss kann man auch alle Zeichen so angeben wie sie beim aufgerufenen Programm ankommen sollen, ohne dass man sich Gedanken machen muss bestimmte Zeichen vor einer Shell zu schützen, weil die für eine Shell eine Sonderbedeutung haben. Das betrifft zum Beispiel Leerzeichen in Dateinamen oder Sonderzeichen bei Passwörtern.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo!
Ein Punkt ist: '-o' ist nicht gleich ' -o '.
'-o ' deshalb, da ich dachte ich könnte auch Leerzeilen übergeben.
Passiert denn noch irgendwas darüber hinaus? Also sozusagen etwas pythonspezifisches? Bisher sieht dein Code nämlich so aus, als ahmst du Shellprogrammierung mit Python nach. Nicht mal das: Du rufst Shellprogramme (wie `copy` und `del`) über Python auf, wo Python für Dateioperationen usw reichlich Funktionalität mitbringt und dafür nicht auf externe Programme angewiesen ist. Da möchte man in der Tat die Hände über den Kopf zusammenschlagen.
Nein, es passiert sonst eigentlich nichts pythonspezifisches. Allerdings soll das ja auf keinen Fall die Endfassung sein. Das ist das, was ich mir jetzt wortwörtlich "zusammengeschustert" habe. Ich war froh, dass es das tut, was es soll ;) - man braucht als Anfänger solche Erlebnisse... Es ist halt die "Idee" eines Anfängers. Nachdem ich bislang leider mit subprocess gescheitert bin, hab ich als ersten Lösungsweg mal den "unsauberen" über die OS Befehle bzw. das Modul system genommen.

Python habe ich deshalb genommen, weil ich es: a.) als eine interessante Aufgabenstellung sehe und b.) auch wenn ich in Python leider noch immer weit weg vom Status eines Anfängers bin, glaube ich, dass die Problemlösung in Python eleganter von statten geht wie in einer Batchdatei.

Es ist "spannender" ein Ziel vor Augen zu haben (Programm), welches im Endeffekt dann auch genutzt werden kann. Z.b. besser als nur Beispielprogramme zu erstellen, die im Endeffekt keinen Nutzen haben. Dummerweise wird mein Pythonengagement immer wieder unterbrochen (wenig Zeit wg. dem Beruf), weshalb ich offenbar kaum vom Fleck komme.

Ich finde das Forum hier übrigens super und möchte mich hier nochmal für eure konstruktive Kritik und eure Geduld bedanken!

LG
Daniel
BlackJack

@mcdaniels: Mit `subprocess` (ungetestet):

Code: Alles auswählen

import os
from glob import glob
from subprocess import PIPE, Popen


def main():
    for pdf_filename in glob('*.pdf'):
        target_path = os.path.join(
            'sig-pdf', os.path.splitext(pdf_filename)[0] + '_sig.pdf'
        )
        print pdf_filename, '->', target_path
        process = Popen(
            [
                'java',
                '-jar', 'PortableSigner.jar',
                '-b', 'de',
                '-c', 'Dieses Dokument wurde amtssigniert. Informationen zur'
                        ' Pruefung finden Sie unter <url einfuegen>',
                '-i', 'Bildmarke.jpg',
                '-n',
                '-t', pdf_filename,
                '-o', target_path,
                '-s', 'acert.p12',
                '-p', 'Passwort'
            ]
            stdout=PIPE
        )
        output, _ = process.communicate()
        return_code = process.wait()
        print 'Return code:', return_code
        print 'Output:'
        print output


if __name__ == '__main__':
    main()
Das Ergebnis wird gleich in das richtige Verzeichnis geschrieben, so dass man keine Verschiebefunktion braucht. Dafür hätte man statt `os.ystem()` mit Hilfe des `shutil`-Moduls etwas Systemunabhängiges schreiben können.

(Die URL habe ich rauseditiert, weil der doofe Syntaxhighlighter vom Forum da Mist baut.)
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo blackjack!

Danke dir für deinen Entwurf. Es hat nur ein Beistrich vor stdout gefehlt. Und ich habe die Printfunktion angepasst (da Python 3):

Code: Alles auswählen

import os
from glob import glob
from subprocess import PIPE, Popen


def main():
    for pdf_filename in glob('*.pdf'):
        target_path = os.path.join(
            'sig-pdf', os.path.splitext(pdf_filename)[0] + '_sig.pdf'
        )
        print (pdf_filename, '->', target_path)
        process = Popen(
            [
                'java',
                '-jar', 'PortableSigner.jar',
                '-b', 'de',
                '-c', 'Dieses Dokument wurde amtssigniert. Informationen zur'
                        ' Pruefung finden Sie unter <url einfuegen>',
                '-i', 'Bildmarke.jpg',
                '-n',
                '-t', pdf_filename,
                '-o', target_path,
                '-s', 'acert.p12',
                '-p', 'Passwort'
            ]
            ,stdout=PIPE
        )
        output, _ = process.communicate()
        return_code = process.wait()
        print ('Return code:', return_code)
        print ('Output:')
        print (output)


if __name__ == '__main__':
    main()
Jetzt muss ich nur noch verstehen, was dein Programm exakt macht. :) Die PDFs werden ordnungsgemäß signiert.

Als Output bekomme ich u.a.:


Return code: 0 (klar)
Output:
b'' (nicht klar)


LG
Daniel
BlackJack

@mcdaniels: Was ist daran nicht klar?

Was es ist? → Die Bytes die das Signierprogramm auf seiner Standardausgabe ausgibt.

Das es Bytes statt Zeichen sind? → Über die Standardein- und ausgaben, sowie in und aus Dateien kann man nur Bytes schreiben und lesen, da im Allgemeinen nicht automatisch ermittelt werden kann in welcher Kodierung diese Daten gelesen und geschrieben werden sollen, wenn es denn überhaupt Texte sind, die man sinnvoll als Unicode-Zeichenketten darstellen kann.

Das es leer ist? → Falls das Signierprogramm etwas ausgibt, dann anscheinend nicht über seine Standardausgabe, sondern über die Standardfehlerausgabe. Dann muss man `stderr` bei `Popen` umleiten und das zweite Element vom `communicate()`-Rückgabewert auswerten.
Antworten