Windows: subprocess.stdout nach file umleiten

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
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

Hallo,

ich möchte ein externes Konsolenprogram aufrufen und den Output in eine Datei umleiten.
Danach die neue Datei mit einer älteren Version vergleichen.
Das geht soweit ich nur eine Datei verabreite.
Wenn ich allerdings das Konsolenprogram in einer Schleige sofort wieder aufrufe und
den Output in eine andere Datei umleite, wird nicht nur der aktuelle Output, sondern
auch der vorherige Output in die neue Datei geschrieben.
Wie kann ich also den stdout löschen um das Program erneut aufzurufen?

Code: Alles auswählen

def run(input_file, args = []):
    newFile = input_file + '.new'
    orgFile = input_file + '.org'
    
    args.append(input_file)
    p = subprocess.Popen(args, stdout=open(newFile, 'w'))
    p.wait()

    compare_result = filecmp.cmp(newFile, orgFile)
    if compare_result:
        os.remove(orgFile)

    return compare_result

if __name__ == "__main__":
    ...
    for subdir, dirs, files in os.walk(test_data_path):
        for file in files:
            if file[len(file) - 4:len(file)] == '.dat':        
                print '{0} compare result is {1}'.format(file, run(test_data_path + file, _arg))
Vielen Dank
Claudia
Zuletzt geändert von Anonymous am Montag 20. August 2012, 15:39, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@myxin: Dein Problem ist das `args`-Argument. Diese leere Liste wird nur *einmal* erstellt und zwar wenn das ``def run(...`` ausgeführt wird und *nicht* etwa jedes mal wenn `run()` *aufgerufen* wird. Dann wird jedes mal das selbe Listenobjekt an `args` gebunden. Und wenn Du das Objekt veränderst, dann ist diese Veränderung bei jedem folgenden Aufruf natürlich „sichtbar”.

Ein weiterer Fehler ist dass Du die Datei in welche die Ausgabe umgeleitet wird nicht explizit schliesst. Das kann so funktionieren, muss es aber nicht. Denn wenn `filecmp.cmp()` ausgeführt wird, dann muss `newFile` komplett geschrieben worden sein, was aber ohne ein `flush()` oder `close()` nicht garantiert ist. Am besten verwendest Du die ``with``-Anweisung für das öffnen von Dateien weil dann garantiert ist, dass die Datei beim Verlassen des Blocks geschlossen wird.

Zu den Namen: Neben `mixedCase`, was in Python unüblich ist nennst Du Werte „File” die keine Dateien repräsentieren, sondern Datei*namen*.

Die Prüfung auf '.dat' ist unglaublich umständlich formuliert. Selbst mit Slicing ginge das einfacher, aber genau für so etwas gibt es die `endswith()`-Methode.

Pfade sollte man mit `os.path.join()` zusammen setzen und nicht mit ``+``.

Und noch ein Fehler: Einfach `test_data_path` vor die Dateinamen von `os.walk` zu setzen funktioniert nicht wirklich sondern nur bei einer Teilmenge von den Dateinamen die da geliefert werden können.
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

@BlackJack,

vielen Dank für die hilfreichen Hinweise.
Das mit args=[] wirft eine Frage auf - die Idee war, das ich sicher sein wollte das
a) args auch eine Liste ist und
b) diese Liste auch nur die nötigen Argumente enthält - das diese dann immer weiter erweitert wird, war mir nicht klar.
Wie wird das normalerweise erledigt?
a) Durch prüfen mit type() ?
b) nach Aufruf von Popen ein Remove() (siehe aktualisierten Code)

Bezüglich
>Einfach `test_data_path` vor die Dateinamen von `os.walk` zu setzen funktioniert nicht wirklich sondern nur bei einer Teilmenge von den Dateinamen die da geliefert werden können.
verstehe ich nicht auf was Du verweist?

Doku
os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])
Generate the file names in a directory tree by walking the tree either top-down or bottom-up. For each directory in the tree rooted at directory top (including top itself), it yields a 3-tuple (dirpath, dirnames, filenames).
Als Beispiel ist dann auch ein Pfad eingesetzt

Code: Alles auswählen

import os
from os.path import join, getsize
for root, dirs, files in os.walk('python/Lib/email'):
    print root, "consumes",
    print sum(getsize(join(root, name)) for name in files),
    print "bytes in", len(files), "non-directory files"
    if 'CVS' in dirs:
        dirs.remove('CVS')  # don't visit CVS directories
Kannst Du mir da noch einen Hinweis geben?

Code: Alles auswählen

def run(input_file, args):
    new_file = input_file + '.new'
    org_file = input_file + '.org'

    
    args.append(input_file)
    print args
    with open(new_file,'w') as f:
        p = subprocess.Popen(args, stdout=f)    
        p.wait()
        f.flush()
    
    args.remove(input_file)
    compare_result = filecmp.cmp(new_file, org_file)
    if compare_result:
        os.remove(org_file)

    return compare_result

if __name__ == "__main__":
    ...
    for dirpath, dirnames, filenames in os.walk(test_data_path):
        for filename in filenames:
            if filename.endswith('.dat'):
                print '{0} compare result is {1}'.format(filename, run(os.path.join(test_data_path,filename), _arg))
Vielen lieben Dank
Claudia
Zuletzt geändert von Anonymous am Montag 20. August 2012, 17:25, insgesamt 2-mal geändert.
Grund: Syntaxhervorherbung aktiviert und Markup verbessert
lunar

@myxin Verwende doch bitte "python"-Tags, damit der Quelltext farblich hervorgehoben wird:

Code: Alles auswählen

[code=python]print 'Hello world'
[/code]
Du kannst diese Formatierung auch über den "python"-Knopf unmittelbar über dem Eingabefeld aktivieren.
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

@lunar

Code: Alles auswählen

def mache_ich_gerne():
    if schon_wieder_vergessen:
        print "I 've told you to use python tag"
    else:
        print 'good girl'
:-)

Gruß
Claudia
BlackJack

@myxin: Man sollte die Liste die bei `args` übergeben wird überhaupt nicht verändern. Weder das Default-Argument, noch wenn der Benutzer da etwas übergibt. Der wäre darüber wahrscheinlich sehr verwundert wenn er das später noch einmal weiter verwenden möchte. Das mit dem `remove()` wäre keine gute Idee, weil das nicht die symmetrische Methode zu `append()` ist. Denn wenn der Wert irgendwo im übergebenen `args` schon vorkommt wird der Wert dort entfernt und nicht der am Ende.

`type()` wird nicht zum Typtesten verwendet. Das würde sowieso dem „duck typing” zuwider laufen. Wenn Du sicherstellen willst, dass die Argumente in einer Liste stehen, dann erzeuge eine neue Liste mit den Argumenten. An die kannst Du dann auch etwas anhängen ohne das Objekt des Aufrufers zu verändern.

Was mir an `run()` fehlt ist das Programm welches aufgerufen wird‽

Der Aufruf von `flush()` ist unnötig, das ist implizit schon im `close()` enthalten, welches implizit im verlassen des ``with``-Blocks enthalten ist.

Das externe Programm kann man mit `subprocess.call()` etwas kürzer aufrufen, denn dort ist der `wait()` aufruf schon enthalten.

Zum `os.walk()`: Du zitierst ja das Beispiel aus der Dokumentation. `files` enthält wirklich nur die Namen der Dateien und keinen Pfadanteil. Schau mal womit in dem Beispiel der Pfad zu den Dateien zusammengesetzt wird. Da wird nicht einfach ein fester Pfad 'python/Lib/email' davor gesetzt, wie Du das machst, sondern der Pfad der auch tatsächlich bis zu den Dateien führt, deren Namen in `files` stehen. Dein Code funktioniert nur für Dateien in dem Verzeichnis in dem `os.walk()` anfängt. Sollte *das* Verzeichnis alles sein was Du benötigst, dann ist `os.walk()` die falsche Funktion, denn dann willst Du ja gar nicht rekursiv über einen Verzeichnisbaum gehen, sondern einfach nur die Dateien in einem Verzeichnis auflisten und verarbeiten. Das wäre dann `os.listdir()` oder schon fertig mit Dateinamensfilter eine Funktion aus dem `glob`-Modul.
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

@BlackJack

vielen Dank für die ausführliche Erklärung.

>Was mir an `run()` fehlt ist das Programm welches aufgerufen wird‽
Meinst Du damit die Definition? Wenn ja, die habe ich absichtlich so gehalten, da verschiedene Konsolenprogramme
aufgerufen werden und deren aktuelle Ausgabe mit einer gespeicherten verglichen. Das tatsächliche Program wird
als Argument in der List _arg übergeben.

Wegen os.walk(), ja, verstanden (denk ich), es muß heissen

Code: Alles auswählen

if __name__ == "__main__":
    ...
    for dirpath, dirnames, filenames in os.walk(test_data_path):
        for filename in filenames:
            if filename.endswith('.dat'):
                print '{0} compare result is {1}'.format(filename, run(os.path.join(dirpath,filename), _arg))
Vielen Dank
Claudia
Antworten