subprocess und mehrere Argumente

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.
Antworten
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Hallo Leute,
ich will versuchen mit subprocess.Popen ein paar Shellscripte zu steuern.

Wenn man die Scripte startet, stellen die einem Fragen, bei denen man Parameter eingeben muss. Die ganze Zeit wurde das mit Shellscripten gelöst, die etwa so aussahen:

Code: Alles auswählen

script.sh << EOF
parameter1
parameter2
EOF
Ich würde das gerne mit subprocess machen, aber bekomme das irgendwie nicht hin, wenn diese Parameter nacheinander übergeben werden müssen.


Was ideal wäre, wäre auch die "Frage" des Scriptes irgendwie zu erfahren, also nicht nur Parameter zu übergeben. Ziel wäre es, nachdem das Python Prog fertig ist, eine Zusammenfassung auszugeben mit den Fragen und den Parametern die ich übergeben habe...

Was ich bis jetzt versucht habe sieht so aus:

Code: Alles auswählen

>>> test = subprocess.Popen('/apps/prod/imagic5/imagic_amd/stand/em2em.e', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> test.communicate()
(' \nPlease specify option                     : ', 'forrtl: severe (24): end-of-file during read, unit 5, file stdin\n')
>>> test.communicate()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/usr/lib/python2.4/subprocess.py", line 1028, in communicate
    self.stdin.flush()
ValueError: I/O operation on closed file
>>> test = subprocess.Popen('/apps/prod/imagic5/imagic_amd/stand/em2em.e', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> test.stdout
<open file '<fdopen>', mode 'rb' at 0x2b61c925d648>
>>> test.stderr
<open file '<fdopen>', mode 'rb' at 0x2b61c925d6c0>
>>> test.stdin 
<open file '<fdopen>', mode 'wb' at 0x2b61c925d5d0>
>>> test.stderr.readlines()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
KeyboardInterrupt
Die communicate Funktion scheint schon in etwa das zu machen, was ich machen will, aber sie gibt mir nur die erste Frage aus und an der Stelle wird dann schon abgebrochen und die Fehlermeldung sehe ich ja da dann auch in dem Tupple...

Kann mir da jemand helfen?
Zuletzt geändert von würmchen am Donnerstag 17. September 2009, 13:10, insgesamt 3-mal geändert.
problembär

Ich denke mal, beim (ersten oder) zweiten Mal mußt Du auch etwas senden:

Code: Alles auswählen

test.communicate("Hallo.\n")
Gruß
lunar

".communicate()" wartet, bis sich der Prozess beendet hat. Man kann ".communicate()" somit kein zweites Mal auf dem selben Objekt aufrufen.

Für "Frage-Antwort-Spielchen" mit dem Unterprozess muss man direkt auf ".stdin" und ".stdout" zugreifen. Sinnvollerweise verwendet man aber eher das pexpect-Modul (oder ruft ein Programm auf, dass sich vernünftig über Optionen steuern lässt).
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Ich bekomme als Antwort wieder die gleiche Meldung wie ohne Argument:

Code: Alles auswählen

>>> import subprocess
>>> test = subprocess.Popen('/apps/prod/imagic5/imagic_amd/stand/em2em.e', stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> test.communicate('TIFF')
(' \nPlease specify option                     :  \nPlease specify option                        : ',
 'forrtl: severe (24): end-of-file during read, unit 5, file stdin\n')

Jetzt habe ich einen Parameter übergeben und er stellt die zweite Frage. Aber er bricht an der Stelle auch wieder ab, wie man in dem Tuple sieht. Ich kann leider nicht mehrere Parameter mit communicate übergeben, ich hab es auch mit einer Liste von Argumenten versucht, aber da steht nur "TypeError: write() argument 2 must be string or read-only buffer, not list"...
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

lunar hat geschrieben:".communicate()" wartet, bis sich der Prozess beendet hat. Man kann ".communicate()" somit kein zweites Mal auf dem selben Objekt aufrufen.

Für "Frage-Antwort-Spielchen" mit dem Unterprozess muss man direkt auf ".stdin" und ".stdout" zugreifen. Sinnvollerweise verwendet man aber eher das pexpect-Modul (oder ruft ein Programm auf, dass sich vernünftig über Optionen steuern lässt).
Danke lunar, wie kann ich auf diese zugreifen? Ein read() oder so geht da anscheinend nicht, oder muss ich da bei dem Popen Aufruf noch was ändern?
lunar

".read()" ohne Parameter blockiert bis alle Daten gelesen wurden. Du musst stattdessen einzelne Bytes oder einzelne Zeilen lesen, und überprüfen, ob eine bestimmte Frage gestellt wurde. Dann kannst Du mit ".stdin.write()" die Antwort schreiben und anschließend wieder zu lesen anfangen.
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Danke für die schnelle Antwort.
Problem ist an der Stelle, wenn ich zB readline() benutze, gibt er mir immer die Zeilen aus, aber die letzte Zeite hat wohl keinen Zeilenumbruch sondern nur ein : als letztes Zeichen und somit gibt er die eigentliche Frage nicht aus, sondern wartet hier bis zu einem Timeout.

Ich hab auch eben mal das pexpect ausprobiert, da komme ich auf das selbe Problem, allerdings ist es auf den ersten Blick ein wenig komfortabler...

Nur hab ich hier das selbe Problem. Auch hier wieder ein Timeout, an der Stelle an der eigentlich die Frage kommen sollte
lunar

Nun, dann musst Du eben byteweise lesen.

Im Bezug pexpect würde ich gerne den problematischen Quelltext sehen. Wenn man pexpect.spawn() nutzt, sollte es eigentlich keine Probleme geben.
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Hi lunar,
danke für Deine Hilfe, mich hat dann der Ehrgeiz gepackt und ich habe mit dem pexpect weiter gearbeitet...

Hier mal noch ein Problem was ich habe:

Code: Alles auswählen

>>> test = pexpect.spawn('/apps/prod/imagic5/imagic_amd/stand/em2em.e')
>>> test.expect(['Please specify option',pexpect.EOF,pexpect.TIMEOUT])
0
>>> test.sendline('TIFF')
5
>>> test.expect(['Please specify option',pexpect.EOF,pexpect.TIMEOUT])
0
>>> test.sendline('MRC')
4
>>> test.expect(['Input = set of 2D sections of a 3D volume',pexpect.EOF,pexpect.TIMEOUT])
0
Das scheint also alles zu funktionieren, danke.
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Irgendwie scheine ich jetzt doch was falsch zu machen...

Kann jemand meinen Fehler sehen?

Hier mal mein Shellscript zum testen:

Code: Alles auswählen

#!/bin/bash

echo -n "Type the year followed by [ENTER]:"

read year

echo "$year was your input"
echo -n "Todays date?:"

read today

echo "$year $today"
Und hier noch die ausgaben aus dem Python interpreter:

Code: Alles auswählen

>>> test = pexpect.spawn('script.sh')
>>> test.expect(['year','Todays',pexpect.EOF,pexpect.TIMEOUT])
0
>>> test.write('test')
>>> test.expect(['year','Todays',pexpect.EOF,pexpect.TIMEOUT])
3
Ich versteh nicht, wieso er mir nach der Eingabe der ersten Variablen abfrage danach einen Timeout bringt. Normal sollte der doch eine zweite frage bringen.
problembär

Schon etwas tricky:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding: iso-8859-1 -*-

import subprocess

p = subprocess.Popen("nameofyourbashscript", stdin = subprocess.PIPE,
                          stdout = subprocess.PIPE,
                          stderr = subprocess.PIPE)

quests = ("Type the year followed by [ENTER]:",
         "Todays date?:")

print quests[0]
for i in quests[0]:
    p.stdout.read(1)
p.stdin.write("2009\n")
print p.stdout.readline().rstrip()
print quests[1]
for i in quests[1]:
    p.stdout.read(1)
p.stdin.write("8\n")
print p.stdout.readline().rstrip()
:wink:

Gruß
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Ok, jetzt ist mir auch subprocess klar. Aber gibts auch ne Möglichkeit p.stdout.read() zu machen, ohne zu wissen welche frage jetzt genau kommt?

Bzw bei dem Programm was ich steuern will kommt oft sehr viel Text als output, also rund 150 Wörter mit vielen Leerzeichen und Tabs und so. Ich denk da ist es etwas unpraktikabel mit dem "guests" zu arbeiten...
problembär

Ok, manchmal muß man etwas tiefer graben, z.B. bis hier:

http://code.activestate.com/recipes/440554/

Damit also:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding: iso-8859-1 -*-

# Code from http://code.activestate.com/recipes/440554/

import os
import subprocess
import errno
import time
import sys

YOURSCRIPTNAME = "yourscriptname"

PIPE = subprocess.PIPE

if subprocess.mswindows:
    from win32file import ReadFile, WriteFile
    from win32pipe import PeekNamedPipe
    import msvcrt
else:
    import select
    import fcntl

class Popen(subprocess.Popen):
    def recv(self, maxsize=None):
        return self._recv('stdout', maxsize)
    
    def recv_err(self, maxsize=None):
        return self._recv('stderr', maxsize)

    def send_recv(self, input='', maxsize=None):
        return self.send(input), self.recv(maxsize), self.recv_err(maxsize)

    def get_conn_maxsize(self, which, maxsize):
        if maxsize is None:
            maxsize = 1024
        elif maxsize < 1:
            maxsize = 1
        return getattr(self, which), maxsize
    
    def _close(self, which):
        getattr(self, which).close()
        setattr(self, which, None)
    
    if subprocess.mswindows:
        def send(self, input):
            if not self.stdin:
                return None

            try:
                x = msvcrt.get_osfhandle(self.stdin.fileno())
                (errCode, written) = WriteFile(x, input)
            except ValueError:
                return self._close('stdin')
            except (subprocess.pywintypes.error, Exception), why:
                if why[0] in (109, errno.ESHUTDOWN):
                    return self._close('stdin')
                raise

            return written

        def _recv(self, which, maxsize):
            conn, maxsize = self.get_conn_maxsize(which, maxsize)
            if conn is None:
                return None
            
            try:
                x = msvcrt.get_osfhandle(conn.fileno())
                (read, nAvail, nMessage) = PeekNamedPipe(x, 0)
                if maxsize < nAvail:
                    nAvail = maxsize
                if nAvail > 0:
                    (errCode, read) = ReadFile(x, nAvail, None)
            except ValueError:
                return self._close(which)
            except (subprocess.pywintypes.error, Exception), why:
                if why[0] in (109, errno.ESHUTDOWN):
                    return self._close(which)
                raise
            
            if self.universal_newlines:
                read = self._translate_newlines(read)
            return read

    else:
        def send(self, input):
            if not self.stdin:
                return None

            if not select.select([], [self.stdin], [], 0)[1]:
                return 0

            try:
                written = os.write(self.stdin.fileno(), input)
            except OSError, why:
                if why[0] == errno.EPIPE: #broken pipe
                    return self._close('stdin')
                raise

            return written

        def _recv(self, which, maxsize):
            conn, maxsize = self.get_conn_maxsize(which, maxsize)
            if conn is None:
                return None
            
            flags = fcntl.fcntl(conn, fcntl.F_GETFL)
            if not conn.closed:
                fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK)
            
            try:
                if not select.select([conn], [], [], 0)[0]:
                    return ''
                
                r = conn.read(maxsize)
                if not r:
                    return self._close(which)
    
                if self.universal_newlines:
                    r = self._translate_newlines(r)
                return r
            finally:
                if not conn.closed:
                    fcntl.fcntl(conn, fcntl.F_SETFL, flags)

message = "Other end disconnected!"

def recv_some(p, t=.1, e=1, tr=5, stderr=0):
    if tr < 1:
        tr = 1
    x = time.time()+t
    y = []
    r = ''
    pr = p.recv
    if stderr:
        pr = p.recv_err
    while time.time() < x or r:
        r = pr()
        if r is None:
            if e:
                raise Exception(message)
            else:
                break
        elif r:
            y.append(r)
        else:
            time.sleep(max((x-time.time())/tr, 0))
    return ''.join(y)
    
def send_all(p, data):
    while len(data):
        sent = p.send(data)
        if sent is None:
            raise Exception(message)
        data = buffer(data, sent)

a = Popen(YOURSCRIPTNAME, stdin=PIPE, stdout=PIPE)
print recv_some(a)
send_all(a, "2009\n")
print recv_some(a)
send_all(a, "8\n")
print recv_some(a, e=0)
:P

Viele Grüße
würmchen
User
Beiträge: 255
Registriert: Mittwoch 7. November 2007, 14:17

Ja, wow, das klappt super, wie ich es mir gedacht habe...

Vielen Dank für die Hilfe!
Antworten