subprocess, popen und Co.

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
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Also irgendwie komme ich nicht weiter... Das neue subprocess ist immer noch ein Rätsel für mich und leider gibt es zu wenig Informationen darüber...

Also am liebesten möchte ich eine Art Shell-Objekt haben den ich ein Befehl schicken kann, Ausgaben auslesen und danach wieder ein neuen Befehl schicken/lesen... Also ungefähr so:

Code: Alles auswählen

ShellObj = os.popen()
ShellObj.write("cd ..")
print ShellObj.read()
ShellObj.write("pwd")
print ShellObj.read()
ShellObj.close()
Das Problem ist, ich möchte einen Befehl Ausführen und danach wissen, wie in der Shell das aktuelle Verz. ist.

Also wenn ich z.B. os.popen("cd ..") ausführe, habe ich keine Möglichkeit mehr zu erfahren, in welchen Verz. man sich jetzt befindet. Denn es wird der Befehl ausgeführt und fertig.
Die einzige Möglichkeit ist die Verkettung von Befehlen, aber das ist super unpraktisch... (Also z.B. "cd ..;pwd")




Außerdem hab ich festgestellt, das nicht wirklich eine PIPE (so wie ich es verstehe) zum Prozess existiert.
Hier mal ein Beispiel:

Code: Alles auswählen

import subprocess
command = "python SleepPrint.py"
process = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE)

print process.stdout.read()
SleepPrint.py:

Code: Alles auswählen

import time

for i in xrange(5):
    print i
    time.sleep(0.3)
Man sieht nicht nach und nach das Aufzählen, sondern das Skript SleepPrint.py wird erst vollständig ausgeführt und danach sieht man die Ausgaben...
Und mein Verständnis von eine PIPE ist es, das ich direkt sehe, wie die print-Ausgaben gemacht werden...

Bin ich da auf dem falschen Dampfer?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

read() liest ja bis zum EOF, und das wird erst erreicht wenn das Script fertig ist. Also müsste man Bytes einlesen (read(1) für ein Byte)... tja, das geht aber bei mir bei meinen Tests auch nicht besser :(
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

Das wird sein Problem nicht lösen. Er ist ein wenig auf dem falschen Dampfer. Das Betriebssystem und/oder die C Bibliothek und/oder Python schreiben nicht jedes Byte sofort, sondern puffern die Daten. Das bedeutet die Daten gehen nur über die Pipe wenn der Puffer voll ist, oder wenn Du explizit verlangst, dass die Daten im Puffer rausgeschrieben werden sollen. Das wird bei Skriptende natürlich automatisch gemacht.

Setz hinter Dein ``print i`` doch mal ein ``sys.stdout.flush()`` und schau ob Du die Daten dann "live" siehst.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Setz hinter Dein ``print i`` doch mal ein ``sys.stdout.flush()`` und schau ob Du die Daten dann "live" siehst.
Jep, das geht! Wobei man kann auch eine Puffergröße mit bufsize bei subprocess() angeben. Ich hab es mit verschiedenen größen versucht, es scheint überhaupt keine Auswirkung zu haben...
Naja, egal... Es funktioniert auch bei "chkdsk c:", sodas man "zwischen Ergebnisse" sieht...
Kann man davon ausgehen, das es je nach Befehl funktioniert oder nicht, abhändig davon ob flush gemacht wird oder nicht?
Das ganze soll mit mein console.py unter Linux laufen...

Im Übrigen kann man statt read(1) besser readline() verwenden. Aber wie eine Schleife aufbauen? Geht das anders besser:

Code: Alles auswählen

while 1:
    line = child_stdout.readline()
    if line == "": break
    sys.stdout.write( line )
Noch jemand eine Idee mit dem "offenhalten" der Shell für einen weiteren Befehl??? Ein child_stdin.write("dir") nach der while-Schleife führt zum Fehler: "IOError: [Errno 22] Invalid argument"
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Kann mir da keiner wieter helfen, oder auch sagen das es nicht geht???
BlackJack

jens hat geschrieben:
BlackJack hat geschrieben:Setz hinter Dein ``print i`` doch mal ein ``sys.stdout.flush()`` und schau ob Du die Daten dann "live" siehst.
Jep, das geht! Wobei man kann auch eine Puffergröße mit bufsize bei subprocess() angeben. Ich hab es mit verschiedenen größen versucht, es scheint überhaupt keine Auswirkung zu haben...
Es hat keine Auswirkungen darauf was das gestartete Programm macht. Ich schrieb ja extra es kann auch beim Betriebssystem und/oder in den Programmen selbst gepuffert werden.
Kann man davon ausgehen, das es je nach Befehl funktioniert oder nicht, abhändig davon ob flush gemacht wird oder nicht?
Jep, davon kannst Du ausgehen.
Im Übrigen kann man statt read(1) besser readline() verwenden. Aber wie eine Schleife aufbauen? Geht das anders besser:

Code: Alles auswählen

while 1:
    line = child_stdout.readline()
    if line == "": break
    sys.stdout.write( line )

Code: Alles auswählen

for line in child_stdout:
   sys.stdout.write(line)

# oder

sys.stdout.writelines(child_stdout)
Noch jemand eine Idee mit dem "offenhalten" der Shell für einen weiteren Befehl??? Ein child_stdin.write("dir") nach der while-Schleife führt zum Fehler: "IOError: [Errno 22] Invalid argument"
Wenn Du eine Shell steuern willst, dann musst Du auch eine Shell starten. Der Parameter `shell` sagt nur ob das Kommando über eine Shell laufen soll oder direkt.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Wenn Du eine Shell steuern willst, dann musst Du auch eine Shell starten. Der Parameter `shell` sagt nur ob das Kommando über eine Shell laufen soll oder direkt.
Jep, das wird es sein... Zumindest unter Windows kann ich so zwei Befehle ausführen lassen:

Code: Alles auswählen

cmd = "cmd.exe"

p = subprocess.Popen(cmd, shell=True, #bufsize=1,
          stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

p.stdin.write("dir c:\n")
p.stdin.write("dir d:\n")
p.stdin.close()

print p.stdout.read()
Allerdings kann man von stdout erst nach dem stdin.close() lesen ;( Ich denke, weil die shell halt noch auf Befehle wartet, ist kein EOF vorhanden...

Unter Linux weiß ich allerdings nicht, welche Entsprechung cmd = "cmd.exe" hat... Mit "sh", "/bin/sh" und "/sbin/sh" bekomme ich immer die Fehlermeldung:

Code: Alles auswählen

: bad interpreter: Datei oder Verzeichnis nicht gefunden
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

jens hat geschrieben:Unter Linux weiß ich allerdings nicht, welche Entsprechung cmd = "cmd.exe" hat... Mit "sh", "/bin/sh" und "/sbin/sh" bekomme ich immer die Fehlermeldung:

Code: Alles auswählen

: bad interpreter: Datei oder Verzeichnis nicht gefunden
/bin/bash
/bin/ksh
usw.
oder /usr/bin/env bash, so wie in Python Scripten.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Aha... Es steckt is os.environ["SHELL"]... Bei mir ist es /bin/bash
Nun klappt es...

Viel hab ich durch die ganze Aktion allerdings nicht gewonnen... Denn in Prinzip konnte man das auch schon mit subprocess.Popen( "ls;pwd" ) erreichen... Denn schade ist, das man eigentlich nicht weiß wo die Ausgabe des ersten Befehls aufhöhrt und wo der zweite Befehl anfängt :( Mit ging es ja ursprünglich darum herraus zu finden, in welchem Verzeichnis man durch irgend ein Befehl gelandet ist...

Code: Alles auswählen

cmd = "cd /test"

p = subprocess.Popen(os.environ["SHELL"], shell=True,
          stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

p.stdin.write( "%s\n" % cmd )
p.stdin.write( "pwd\n" )
p.stdin.close()

txt = p.stdout.read()
txt = txt.split()
print "-"*80
print "\n".join(txt[:-1])
print "-"*80
print "Aktuelles Verz.:", txt[-1]
print "-"*80
BlackJack

Vielleicht kann Pexpect das was Du willst. Damit kann man angeblich Kommandozeilenprogramme in der Art steuern wie Du das möchtest.
BlackJack

Ich habe mir Pexpect mal angeschaut. Sowas hier funktioniert:

Code: Alles auswählen

#!/usr/bin/env python
import pexpect

command = 'ls --color'
shell_prompt = r'sh-(.*)\$'

shell = pexpect.spawn('sh')
shell.expect(shell_prompt)
shell.sendline(command)
shell.expect(shell_prompt)
command_output = shell.before
shell.sendline('pwd')
shell.expect(shell_prompt)
shell.sendeof()
working_directory = shell.before.split('\n')[-2]

print command_output
print '$PWD =', working_directory
Eventuell musst Du den regulären Ausdruck für den Shell-Prompt anpassen.

Aber ich stelle mir die Frage, was Du machen willst wenn jemand zum Beispiel einfach nur ``cat`` eingibt!?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich hab mich dazu entschieden, die "cd" Befehle abzufangen und selbst auszuführen... Somit weiß ich in welchem Verz. man sich befindet... Wenn nun Programme eigenständig das Verz wechseln, dann bekomme ich davon natürlich nichts mit... Aber ich kenne spontan eh keins was das macht.

Mit "cat" oder auch "top" gibt es natürlich Probleme... Wäre zu überlegen, ob man nicht die Laufzeit der gestarteten Programme einschränken könnte... Denn die laufen ja weiter... Wenn man so ein Programm gestartet hatte, kann man den Server auch nicht richtig beenden...

Müßte ich dazu was mit Threading machen? Damit kenne ich mich noch überhaupt nicht aus...
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Du könntest es als Threads starten, deren Lebenszeit kannst du auch beeinflussen, so wies es im Cookbook steht. Allgemein ist das Kapitel über Threads sehr nützlich.

Was mich interessieren würde, sind noch kleinere Threads, sogenannte greenlets, denn Threads sind manchmal problematisch.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

OK, ich hab mir hier mal was zusammen gebasteln, um zu testen, wie man eine Art "Timer" bei subprocess realisieren kann:

Code: Alles auswählen

import os, sys, threading, subprocess, time, signal


from optparse import OptionParser
OptParser = OptionParser()
options, args = OptParser.parse_args()

if len(args) != 0:
    # for-test-Schleife
    for i in xrange(10):
        print i
        sys.stdout.flush()
        time.sleep(0.2)
    print "for-test-Schleife abgelaufen!"
    sys.exit()



class MyThread(threading.Thread):
    def __init__( self, command, *args, **kw):
        self.command = command
        threading.Thread.__init__(self,*args,**kw)

    def readOutData( self, readObj ):
        "Ausgaben vom subprocess ausgeben"
        while 1:
            line = readObj.readline()
            if line == "": break
            sys.stdout.write( line )

    def run(self):
        "Führt per subprocess einen den Befehl 'self.command' aus."
        print "Starte '%s'..." % self.command,
        self.process = subprocess.Popen(
                self.command,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
        print "OK"
        print "Lese Ausgaben:"
        print "-"*80
        self.readOutData( self.process.stdout )
        self.readOutData( self.process.stderr )
        print "-"*80

    def stop( self ):
        """
        Testet ob der Prozess noch läuft, wenn ja, wird er mit
        os.kill() (nur unter UNIX verfügbar!) abgebrochen.
        """
        if self.process.poll() != None:
            print "Prozess ist schon beendet"
            return

        print "Prozess abbrechen...",
        os.kill( self.process.pid, signal.SIGQUIT )
        print "OK"


test=MyThread( "python %s test" % __file__ )
test.start()    # thread starten
test.join(1)    # 1sek laufen lassen
test.stop()     # Prozess abbrechen

print "Fertig!"
Das Skript ruft per subprocess sich selber mit einem Parameter ("test") auf. Damit trifft die obere if-Anweisung zu und es wird eine for-Schleife gestartet, die einfach eine Zahl ausgibt...
Die for-test-Schleife dauert allerdings länger, als die 1sek. die mit "join(1)" festgelegt wurde. Somit wird stop() ausgeführt und der Prozess abgebrochen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Da ich genau die selbe Funktion für PyLucid benötige, hab ich nochmal das ganze überarbeitet:

Code: Alles auswählen

class subprocess2(threading.Thread):
    """
    Allgemeine Klasse um subprocess mit einem Timeout zu vesehen.

    Da os.kill() nur unter Linux und Mac verfügbar ist, funktioniert das
    ganze nicht unter Windows :(
    """
    def __init__( self, command, cwd, timeout ):
        self.command    = command
        self.cwd        = cwd
        self.timeout    = timeout

        self.killed = False # Wird True, wenn der Process gekillt wurde
        self.out_data = "" # Darin werden die Ausgaben gespeichert

        threading.Thread.__init__(self)

        self.start()
        self.join( self.timeout )
        self.stop()

        # Rückgabewert verfügbar machen
        self.returncode = self.process.returncode

    def run(self):
        "Führt per subprocess den Befehl 'self.command' aus."
        self.process = subprocess.Popen(
                self.command,
                cwd     = self.cwd,
                shell   = True,
                stdout  = subprocess.PIPE,
                stderr  = subprocess.STDOUT
            )

        # Ausgaben speichern
        while 1:
            line = self.process.stdout.readline()
            if line == "": break
            self.out_data += line

    def stop( self ):
        """
        Testet ob der Prozess noch läuft, wenn ja, wird er mit
        os.kill() (nur unter UNIX verfügbar!) abgebrochen.
        """
        if self.process.poll() != None:
            # Prozess ist schon beendet
            return

        self.killed = True
        os.kill( self.process.pid, signal.SIGQUIT )
Beispiel Aufruf:

Code: Alles auswählen

    import os, subprocess, threading, signal
    
    process = subprocess2( "top", "/", timeout = 2 )
    
    if process.killed == True:
        print "Timout erreicht! Prozess wurde gekillt."
    print "Exit-Status:", process.returncode
    print "Ausgaben:", process.out_data

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Leider klappt es nicht ganz mit der ermittlung ob der Prozess noch läuft oder nicht :(
Unter Python v2.2.1 auf Hosteurope (mit nachgerüstetem subprocess) ist process.poll() immer gleich "None" :(
Lokal bei mir mit Python 2.4.1 unter debian liefert poll mit normale Werte...

Wie kann ich noch feststellen, ob der subprocess beendet ist???

EDIT 1: Ach eine andere Variante ist einfach das versuchte kill ;)

Code: Alles auswählen

        try:
            os.kill( self.process.pid, signal.SIGQUIT )
        except OSError:
            # Process war schon beendet
            pass
        else:
            # Process mußte beendet werden
            self.killed = True
Die Variante funktioniert wiederum nur bei Hosteurope, weil dort eine Exception auftritt, falls der Prozess nicht mehr existiert... Hingegen gibt es bei mit lokal keine Exception...
Somit kann ich zwar einen Timeout setzten bzw. ausführen, aber ich weiß nicht ein Timeout überhaupt aufgetreten ist :( Das möchte ich aber gern wissen...

EDIT 2: Soeben ist mir eine etwas blöde Art eingefallen... Ich teste einfach die Ausführungszeit. Wenn diese gleich oder größer als der timeout ist, wurde der Prozess gekillt:

Code: Alles auswählen

class subprocess2(threading.Thread):
    """
    Allgemeine Klasse um subprocess mit einem Timeout zu vesehen.

    Da os.kill() nur unter Linux und Mac verfügbar ist, funktioniert das
    ganze nicht unter Windows :(

    Beispiel Aufruf:
    ---------------------------------------------------------
    import os, subprocess, threading, signal

    process = subprocess2( "top", "/", timeout = 2 )

    if process.killed == True:
        print "Timout erreicht! Prozess wurde gekillt."
    print "Exit-Status:", process.returncode
    print "Ausgaben:", process.out_data
    ---------------------------------------------------------
    """
    def __init__( self, command, cwd, timeout ):
        self.command    = command
        self.cwd        = cwd
        self.timeout    = timeout

        self.killed = False # Wird True, wenn der Process gekillt wurde
        self.out_data = "" # Darin werden die Ausgaben gespeichert

        threading.Thread.__init__(self)

        start_time = time.time()
        self.start()
        self.join( self.timeout )
        self.stop()
        duration_time = time.time() - start_time

        if duration_time >= timeout:
            # Die Ausführung brauchte zu lange, also wurde der Process
            # wahrscheinlich gekillt...
            self.killed = True

        # Rückgabewert verfügbar machen
        try:
            self.returncode = self.process.returncode
        except:
            self.returncode = -1

    def run(self):
        "Führt per subprocess den Befehl 'self.command' aus."
        self.process = subprocess.Popen(
                self.command,
                cwd     = self.cwd,
                shell   = True,
                stdout  = subprocess.PIPE,
                stderr  = subprocess.STDOUT
            )

        # Ausgaben speichern
        self.out_data = self.process.stdout.read()

    def stop( self ):
        try:
            os.kill( self.process.pid, signal.SIGQUIT )
        except OSError:
            # Process war schon beendet
            pass

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Das ganze hab ich nochmal aktualisiert, siehe:
http://www.python-forum.de/post-96164.html#96164
http://trac.pylucid.net/browser/trunk/p ... y?rev=1634

EDIT: Im neuen Thread gehts weiter: http://www.python-forum.de/topic-14301.html
EDIT2: Link aktualisiert.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten