Subprocess-Modul

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.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Hallo miteinander.

Ich habe eine Frage bezüglich Verwendung des subprocess-Moduls.
Ich habe einen Text gegeben, der noch zu tokenisieren ist. Für das Tokenisieren habe ich eine Datei (tok.py) gemacht, die funktioniert.
Um den Text nun zu tokenisieren, könnte man ja einfach die Datei tok.py imporrtieren.
Nun möchte ich aber das subprocess-Modul benutzen. Hierzu muss der Text auf stdin eingehen und per stdout müsste der Text dann tokenisiert als String rauskommen. Um dann eine Liste zu erhalten, kann man ja einfach .split() auf die Ausgabe des Programms anwenden.

Hierzu habe ich folgendes probiert:

Code: Alles auswählen

import shlex, subprocess

command = /home/exercise -input text.txt -output 'out.txt' -cmd python tok.py 
args = shlex.split(command)
p = subprocess.Popen(args)
Allerdings bin ich mit dem subprocess-Modul nicht allzu vertraut, deshalb wäre ich sehr dankbar um Hilfe.
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

MarcelF6 hat geschrieben:Hallo miteinander.

Ich habe eine Frage bezüglich Verwendung des subprocess-Moduls.
Ich habe einen Text gegeben, der noch zu tokenisieren ist. Für das Tokenisieren habe ich eine Datei (tok.py) gemacht, die funktioniert.
Um den Text nun zu tokenisieren, könnte man ja einfach die Datei tok.py imporrtieren.
Nun möchte ich aber das subprocess-Modul benutzen. Hierzu muss der Text auf stdin eingehen und per stdout müsste der Text dann tokenisiert als String rauskommen. Um dann eine Liste zu erhalten, kann man ja einfach .split() auf die Ausgabe des Programms anwenden.

Hierzu habe ich folgendes probiert:

Code: Alles auswählen

import shlex, subprocess

command = /home/exercise -input text.txt -output 'out.txt' -cmd python tok.py 
args = shlex.split(command)
p = subprocess.Popen(args)
Allerdings bin ich mit dem subprocess-Modul nicht allzu vertraut, deshalb wäre ich sehr dankbar um Hilfe.
Ich kenn mich mut subprocess auch nicht aus, was mir aber auffällt:

Code: Alles auswählen

command = /home/exercise -input text.txt -output 'out.txt' -cmd python tok.py 

Wie soll das gehen?
Ich würde mal das probieren:

Code: Alles auswählen

command = '/home/exercise -input text.txt -output \'out.txt\' -cmd python tok.py'
und wo ich's grad sehe, wieso sind bei dem out.txt die ' ' da und bei text.txt und tok.py nicht?
und noch was, ich hoffe das dieser komplizierte weg nur als "Übung" dient, wie man das Modul her nimmt.
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Danke für die schnelle Antwort.
Ich habs so versucht, wie's du vorgeschlagen hast. Leider funktioniert es so noch nicht.
Die " ' " bei output waren zuviel. Aber auch ohne die Zeichen klappt es noch nicht.
Ja genau, es soll nur zur Übung dienen. Denn wie erwähnt..hätte ich diese Vorgabe nicht, würde ich tok.py einfach per import-Statement importieren...
Py-Prog
User
Beiträge: 673
Registriert: Dienstag 16. Februar 2010, 17:52
Wohnort: G:\ermany

Jetzt glaub ich weiß ich warum:

Code: Alles auswählen

command = '/home/exercise python tok.py -input text.txt -output out.txt'
so irgendwie, ich weiß ja nicht ganau was die das -cmd bedeuten soll, aber ich würde einfach mal sagen das das python tok.py ganz vorn hin muss du schreibst doch auch:

Code: Alles auswählen

func(arg)
und nicht

Code: Alles auswählen

(arg)func
oder?
Technik ist: wenn alles funktioniert und keiner weiß warum.
Wer Rechtschreibfehler findet darf sie behalten.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Mh :D
Ja, du hast Recht! ..sorry wegen der Verwirrung.
Tatsächlich funktioniert das auch, bis und mit der Zeile
args = shlex.split(command)

Nun möchte ich ja den Text in text.txt mit Hilfe der Datei tok.py tokenisieren und als Liste speichern. Ich hab das mit subprocess.Popen(args) probiert - aber so einfach scheint es dann doch nicht zu gehen, oder?
[Ich hab das zwar so bei einem Beispiel gelesen, das ich im Internet gefunden habe...]
BlackJack

@MarcelF6: Ich denke Du solltest mal Dein Problem beschreiben. Was genau funktioniert denn nicht? Was ist ``/home/exercise`` für ein Programm? Und liegt das wirklich auf der Ebene wo normalerweise die Heimatverzeichnisse abgelegt werden? Wer speichert denn ausgerechnet *dort* ausführbare Programme?

Funktioniert diese Kommandozeile denn wie gewünscht wenn Du sie direkt in einer normalen Shell, also völlig ohne Python eingibst?
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Ok.
Ich habe einen Text gegeben, sagen wir text.txt, der im Verzeichnis /home/exercise gespeichert ist. (Der Verzeichnis-Name ist natürlich etwas grösser, aber um es hier zu modellieren reicht es.)
Dieser Text muss ich zuerst tokenisieren. Dafür habe ich die Datei tok.py zur Verfügung, die im selben Verzeichnis gespeichert ist. Diese Datei soll ich aber nicht per import ausführen, sondern mit dem subprocess Modul. Das Programm tokenisiert den auf stdin eingehenden Text und gibt den tokenisierten Text auf stdout als String aus. Auf die Ausgabe würde ich dann auch gleich .split() anwenden, damit ich eine Liste mit den tokenisierten Wörtern erhalte.

..wie erwähnt, das home/exercise ist kein Programm, sondern das Verzeichnis, wo die Datei und der Text liegt.
Was nicht funktioniert ist schwierig zu sagen..ein Fehler erscheint erst auf der Zeile subprocess.Popen(args).
Nein, normalerweise wird natürlich nicht in diesem Verzeichnis gearbeitet. Aber alle notwendigen Daten liegen in diesem Verzeichnis - und auch auf der shell arbeite ich momentan nur in diesem Verzeichnis.
Wenn ich alles genauso in die shell eingebe, klappts ebenfalls bis zur gleichen Stelle. Dann erscheint:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/subprocess.py", line 672, in __init__
errread, errwrite)
File "/usr/lib/python2.7/subprocess.py", line 1213, in _execute_child
raise child_exception
OSError: [Errno 13] Permission denied
BlackJack

@MarcelF6: Wenn Du das in der Shell eingibst wird das ganz sicher auch nicht funktionieren, eben weil ``/home/excercise`` kein Programm ist. Da passiert dann so etwas hier:

Code: Alles auswählen

bj@s8n:~$ /home/exercise python tok.py -input text.txt -output out.txt
bash: /home/exercise: No such file or directory
Wenn Programm und Eingabetextdatei in dem Verzeichnis liegen, dann musst Du entweder dafür sorgen, dass die Pfade immer *komplett* angegeben werden, oder dass das Arbeitsverzeichnis des gestarteten Prozesses dort hin verlegt wird.

Versteht `tok.py` denn die ``-input`` und ``-output`` Argumente? Falls nein, dann sind die natürlich Unsinn. Falls ja bleibt immer noch das Problem, dass in Deiner Aufgabenbeschreibung steht Du sollst mit dem `tok.py` über Standardein- und -ausgabe kommunizieren — also nicht über Argumente.

Arbeite doch mal die Dokumentation zum `subprocess`-Modul durch — komplett. `Popen()` hat eine Menge Argumente die man mitgeben kann, und der Rückgabewert einige nützliche Methoden. Und dazu bräuchtest Du vielleicht aus einer allgemeineren Quelle Informationen wie sich das mit Prozessen, Standardein- und -ausgaben und deren Umleitung verhält.

Das sieht mir bisher jedenfalls nach planlosem rumprobieren und dann gleich mal nach der Lösung zur Hausaufgabe fragen aus.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Mittlerweile habe ich die Doku step-by-step durchgelesen und probiert, das Modul anzuwenden.
Folgendes ist rausgekommen: (die "..." habe ich der Übersicht halber eingefügt)

Code: Alles auswählen

import subprocess, os, sys
exepath = os.path.join("/home/.../text.txt")
cmd = [exepath]
process = subprocess.Popen("/home/.../python tok.py", cmd, stdout=subprocess.PIPE)
outputstring = process.communicate()[0]
Nun erhalte ich den folgenden Fehler:
process = subprocess.Popen(cmd,"/home/.../python tok.py",stdout=subprocess.PIPE)
File "/usr/lib/python2.7/subprocess.py", line 622, in __init__
raise TypeError("bufsize must be an integer")
TypeError: bufsize must be an integer
Meiner Meinung nach dürfte der Code nicht allzu falsch sein. Einzig beim Aufruf von " python tok.py " bin ich nicht allzu sicher.
Zudem werde ich den Verdacht nicht los, dass mit dem installierten subprocess-Modul auf meinem Computer etwas nicht stimmt..
Zuletzt geändert von MarcelF6 am Montag 9. April 2012, 10:07, insgesamt 1-mal geändert.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Schau mal ganz scharf hierauf: http://docs.python.org/library/subproce ... onstructor

Danach sollte dir auch die Fehlermeldung klar sein.

Wenn du das getan hast: Auch der Pfad zum Programm ist teil von `args` und sollte in deinem `cmd` sein.

Wuerdest du tok.py anpassen und den Code in eine main-Funktion schreiben, koenntest du uebrigens den subprocess Umweg vermeiden ..
BlackJack

@cofi: Soweit ich das verstanden habe ist das nicht erlaubt in der Hausaufgabe, sondern *soll* mit `subprocess` gelöst werden.

@MarcelF6: Trotzdem könnte und sollte man die `tok.py` so schreiben, dass man sie leichter als Modul wiederverwenden kann. Und auch so, dass man sie als Programm ausführen kann, also mit Shebang-Zeile am Anfang und gesetztem „execute”-Bit bei den Dateirechten.

Der `readlines()`-Aufruf in dem Skript ist übrigens überflüssig und verbraucht nur unnötig Arbeitsspeicher und für das letzte `re.sub()` sind reguläre Ausdrücke ein wenig überdimensioniert.

Nachdem ich jetzt in der `subprocess`-Dokumentation gesehen habe wie Du am Anfang auf Optionen wie ``-input``, ``-output``, und ``-cmd`` gekommen bist, würde ich dringend dazu raten die Programme grundsätzlich anders zu entwickeln: Nicht irgend wo anders etwas abschreiben und dann versuchen das so lange zu verändern, bis das gewünschte Ergebnis heraus kommt, sondern das eigene Programm Stück für Stück von Grund auf selbst schreiben und jeden Schritt testen. Erst mit dem nächsten Schritt weiter machen, wenn der Teilschritt funktioniert. Übertragen auf den konkreten Fall, könntest Du erst einmal ein externes Skript ausführen dass keine Eingabe erwartet und nur etwas auf seine Standardausgabe ausgibt. Wenn das funktioniert, im nächsten Schritt diese Ausgabe in Deinem Programm auslesen. Und in einem letzten Schritt dann das andere Programm so erweitern, dass es auch eine Eingabe erwartet und diese von dem Starterprogramm übergeben. Das ist ja im Moment auch noch etwas was bei Deinem Versuch fehlt.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Mittlerweile bin ich etwas weiter gekommen, was das Verständnis des subprocesses angeht.
Auf dieser Seite: http://jimmyg.org/blog/2009/working-wit ... ocess.html
habe ich auch noch ein paar wertvolle Angaben gefunden.
Damit sieht mein Code nun wie folgt aus:

Code: Alles auswählen

import subprocess
f = open("/home/.../text.txt", "r")
chris = str(f.readlines())
#chris = chris.replace("\n","")
f.close()
process = subprocess.Popen(['python','/home/.../tok.py'], shell=False, stdin=subprocess.PIPE)
r = process.communicate(chris)
Ich habe noch 2 Probleme: Zum einen möchte ich gerne die Absatz-Zeichen ("\n") los werden, und zum anderen wird die tok-Datei nicht korrekt geöffnet - bzw. der Text wird nicht korrekt bearbeitet.
Weil was ich (ohne Fehlermeldungen) zurück erhalte ist der Text von text.txt als Liste, inkl. den Absatzzeichen, aber eben noch nicht tokenisiert.
Ich habe die Direkt-Eingaben, die im angegebenen Link gemacht werden, auch probiert und habe das hier analog aufgebaut.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Du erhältst eine Liste, da der str-Aufruf in Zeile drei nicht das macht, was du erwartest. ``readlines`` liefert dir eine Liste von Zeilen, ``str`` sorgt dafür, dass ein String aus dieser Liste erzeugt wird. Das kannst du ganz einfach im Interpreter testen:

Code: Alles auswählen

>>> str([1,2,3,4])
'[1, 2, 3, 4]'
Wenn du den gesamten Inhalt der Datei einlesen willst, dann verwende einfach die read-Methode des file-Objekts. Die Newlines kannst du dann mittels ``replace`` entfernen.

Außerdem solltest du Dateien mittels with-Statement öffnen, dann wird die Datei auch im Fehlerfall geschlossen und du kannst das Schließen auch nicht vergessen:

Code: Alles auswählen

with open("...") as f:
    chris = f.read()
Das Leben ist wie ein Tennisball.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Sehr gut, vielen Dank euch allen!

Nun habe ich:

Code: Alles auswählen

import subprocess
f = open("/home/.../text.txt", "r")
chris = f.read()
chris = chris.replace("\n","")
f.close()
process = subprocess.Popen(['python','tok.py'], shell=False, stdin=subprocess.PIPE)
output = process.communicate(chris) #ist tuple
output.split() #geht wegen tuple nicht.
(das mit open/close hab ich jetzt noch drin - werde es dann aber durch " with open... " ersetzen)

Was jetzt noch suboptimal ist: output wird als tuple ausgegeben, das ich dann nicht mehr per split() splitten kann. Ich habe es mit str(process.communicate(chris)).split() probiert, aber das gibt mir dann nur eine leere Liste. Wie kann ich dieses Problem in den Griff kriegen? Die Ausgabe (wieso kommt die "automatisch", wenn ich die Zeile " output = process.communicate(chris) " ohne print statement schreibe?) ist zwar korrekt, aber wie gesagt. Ich möchte sie dann ja noch gerne gesplittet in einer Liste speichern.

Besten Dank für die Hilfe!
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Lesen: http://docs.python.org/library/subproce ... ommunicate

Die meisten `communicate` Aufrufe sehen uebrigens so aus:

Code: Alles auswählen

stdout, stderr = proc.communicate(..)
Und dann wirst du keine Ausgabe bekommen bis du auch stdout in ein subprocess.PIPE leitest.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Vielen Dank!
Nun funktioniert eigentlich alles.
Ich habe nur noch eine Frage: Warum ist res nachher nicht mehr aufrufbar?

Code: Alles auswählen

process = subprocess.Popen(['python','tok.py'], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  stdin = chris
  stdin = stdin.split()
  res = process.communicate(str(stdin))
Wenn ich nämlich per print-Statement res ausgeben möchte, erscheint nur noch: []
Wie kann ich res "richtig" speichern, also mit dem gesamten Inhalt von process.communicate(str(stdin))?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Die Beschreibung passt nicht zu _dem_ Code. `print res` wird hier _immer_ ein Tupel ausgeben, keine Liste.

Also bitte zeig uns den Code um den es eigentlich geht.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Sorry, hier der momentane Code:

Code: Alles auswählen

import subprocess, os, sys
  f = open("/home/.../text.txt", "r")
  chris = f.read()
  chris = chris.replace("\n","")
  f.close()
  process = subprocess.Popen(['python','tok.py'], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  stdin = chris
  stdin = stdin.split()
  res = process.communicate(str(stdin))
Die letzte Zeile gibt eine Liste aus mit den tokenisierten Wörter - also genau das, was ich eigentlich möchte.
Aber diese lässt sich nicht speichern, denn eigentlich bräuchte ich diese Liste als Argument für eine andere Funktion. Aber res ist leer, also: res = [].
Wie kann ich also diese Liste in einer Variable speichern, die ich nachher weiter verwenden kann?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nochmal: Deine Beschreibung passt nicht zum Verhalten von subprocess.

Code: Alles auswählen

In [1]: import subprocess

In [2]: p = subprocess.Popen(['ls', '/'], stdout=subprocess.P
subprocess.PIPE   subprocess.Popen  

In [2]: p = subprocess.Popen(['ls', '/'], stdout=subprocess.PIPE)

In [3]: p.communicate()
Out[3]: 
('base.key\nbin\nboot\ndev\netc\nhome\ninitrd.img\ninitrd.img.old\nlib\nlib32\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nselinux\nsrv\nsys\ntmp\nusr\nvar\nvmlinuz\nvmlinuz.old\n',
 None)

In [4]: _[0]
Out[4]: 'base.key\nbin\nboot\ndev\netc\nhome\ninitrd.img\ninitrd.img.old\nlib\nlib32\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nselinux\nsrv\nsys\ntmp\nusr\nvar\nvmlinuz\nvmlinuz.old\n'
Das Ergebnis von `communicate` ist _immer_ ein Tupel, keine Liste, und Stdout das erste Element davon. Stdout wiederum ist _immer_ ein String mit genau der Ausgabe, s.o., keine Liste.

Also bitte zeige mal den wirklichen Inhalt von rest (`repr(res)`) und dazu die Ausgabe von `python tok.py < text.txt`.
MarcelF6
User
Beiträge: 226
Registriert: Samstag 3. März 2012, 21:30

Ahh...vielen Dank!! :)
Du hast mich mit repr(...) gerade auf einen Fehler hingewiesen. Ich versuchte immer, communicate() weiter zu verarbeiten - dabei muss/soll/darf/kann ich das ja gar nicht.
Mit Hilfe von repr(...) konnte ich den Fehler aber beheben - nun funktioniert alles wie gewünscht.
Herzlichen Dank an alle!
Antworten