subprocess.Popen() - Stream stdout / stdin /stderr

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
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Hey liebe leute,
ich bin aktuell dabei den Installer für unserer Software easyPANEL in Python zu schreiben.

Es klappt auch alles ganz gut, nur habe ich ein Problem. Es muss zuvor ein Update vom System gemacht werden bevor ich die Software für easyPANEL installieren kann.
Nun habe ich folgendes gemacht.

Code: Alles auswählen

def set_install_software():
    proc = Popen('apt-get remove htop',
                           shell=True,
                           stdout=PIPE,
                           stdin=PIPE
                           )
        
    while proc.poll() is None:
        output = proc.stdout.readline()
        stdout.write(output)


if __name__ == "__main__":
    set_install_software()
Das apt-get remove ist nur ein Beispiel... Folgedes spielt sich nun ab.
Ich erhalte die Interaktion mit der shell, bekomme die ausgabe und kann aggieren. Das problem ist, man wird gefragt ob man wirklcih fortfahren will
apt-get remove htop
Paketlisten werden gelesen... Fertig
Abhängigkeitsbaum wird aufgebaut.
Statusinformationen werden eingelesen.... Fertig
Die folgenden Pakete werden ENTFERNT:
htop
0 aktualisiert, 0 neu installiert, 1 zu entfernen und 1 nicht aktualisiert.
Nach dieser Operation werden 216 kB Plattenplatz freigegeben.
Möchten Sie fortfahren [J/n]?
Die letzte zeile bekomme ich nicht ausgegeben... Nun habe ich aufen Unixboard nachgefragt.
UnixBoard Thread Subprocess

Die haben mich hierher verwiesen. :-)

Einer von euch kann mir doch sicherlich helfen, oder ?

Danke euch.

Gruß
BlackJack

@Nexus633: Warum verwendest Du hier denn `PIPE`? Wenn Du nichts anderes machst als `stdout` auszulesen um es dann auf `sys.stdout` auszugeben, nichts anderes macht `print()` ja, dann kannst Du die Umleitung auch einfach bleiben lassen.

Wo/wie führst Du das denn aus? Hoffentlich nicht in einer IDE oder so sondern direkt in einer Konsole‽ Und warum ``shell=True``? Dafür gibt es hier keinen Grund.
Nexus633
User
Beiträge: 8
Registriert: Freitag 19. Dezember 2014, 23:02

Hallo,
ich bin ein anfänger und teste mich gerade in die Python ein.
Ich kann ja mal den gesamten Code schicken...

Der Code steht in einer install.py datei und wird über die Shell ausgeführt. ./install.py

oO ihr werdet mich sicherlich verfluchen....
Kurz noch zur PIPE... Das habe ich aus dem Unixboard und aus sämtlichen Threads im Internet.

Code: Alles auswählen

#!/usr/bin/env python
###########################################
#                                         #
# 	esay-PANEL Webinterface           #
# 	Copyright 2012-2015 easy-Panel    #
# 	Marc Schmeer & Andre Rohlf        #
# 	www.easy-panel.de                 #
#                                         #
# 	Version    : 2.0.0.5              #
# 	Datum      : 06.01.2015	          #
#                                         #
###########################################

# Imports
from __future__ import absolute_import, print_function
from time import sleep
from subprocess import *
import platform as info
from sys import *
import os

class System_Check:
    def check_os(self):
        system_info_list = []
        distname = info.dist()[0] # Which OS is Installed
        system_info_list.append(distname)
        
        version = info.dist()[1] # Which version of OS
        system_info_list.append(version)
        
        arch = info.machine()
        system_info_list.append(arch) # Which arch of OS
        
        kernel = info.uname()[2]
        system_info_list.append(kernel) # Which in Installed
        
        return(system_info_list)
 
 
class System_Install:
    def welcome(self):
        say_welcome_text = "Welcome to install easyPANEL.\n" \
            "In the neighbor will be presented to you options for installing the software below.\n" \
            "Read the routine well and decide what you need for your system.\n\n"
        
        say_note_text = "NOTE: Needless Installed software can lead to performance problems.Install only what you need\n"
        say_text = "{0}{1}".format(say_welcome_text, say_note_text)
        
        os.system("clear")
        print(say_text)
        while True:
            next_page = raw_input("Begin installation ? [Y/n]: ").lower()
            yes = set(['yes', 'Yes', 'YES', 'y', 'Y', ''])
            no = set(['no', 'No', 'NO', 'n', 'N'])
            if next_page in yes:
                self.set_language()
                break
            elif next_page in no:
                break
            else:
                print("\vPlease respond with 'yes' or 'no' ('y' or 'n')")
 
 
    def set_language(self):
        say_lang_text = "Please select your language to continue the installation.\n\n"
        say_lang_choice = ["1. German", "2. English"]
        say_lang = "{0}{1}".format(say_lang_text, say_lang_choice)
        os.system("clear")
        print(say_lang)
        
        while True:
            next_page = raw_input("\nPlease choose your Language [1/2]: ").lower()
            German = set(['1', 'g', ''])
            English = set(['2', 'e'])
            if next_page in German:
                self.get_detect_system(0) # lang = 0, important for say_detected_os_fomat
                break
            elif next_page in English:
                self.get_detect_system(1) # lang = 1, important for say_detected_os_fomat
                break
            else:
                print("\vPlease respond with '1' or '2' ('g' or 'e')")
 
 
    def get_detect_system(self, lang):
        system = System_Check()
        detected_os = system.check_os()[0]
        detected_version = system.check_os()[1]
        detected_arch = system.check_os()[2]
        detected_kernel = system.check_os()[3]
        
        say_detected_os_fomat = [
            "Folgende Informationen zu Ihrem System wurden festgestellt:\n\n" \
            "OS: \t\t{0}\nVersion: \t{1}\nArchitektur: \t{2}\nKernel: \t{3}\n\n" \
            "Sind diese Daten korrekt ?", \
            "The following information about your system have been detected:\n\n" \
            "OS: \t\t{0}\nVersion: \t{1}\nArchitektur: \t{2}\nKernel: \t{3}\n\n"
        ]  # Information of System
        
        say_detected_os = say_detected_os_fomat[lang].format(detected_os, detected_version, detected_arch, detected_kernel)
        os.system("clear")
        
        print(say_detected_os)
        self.set_install_software(lang)
 
    def set_install_software(self, lang):
        proc = Popen('apt-get remove htop',
                               shell=True,
                               stdout=PIPE,
                               stdin=PIPE,
                               stderr=PIPE,
                               bufsize=2
                               )
        
        while proc.poll() is None:
            output = proc.stdout.readline()
            stdout.write(output)


if __name__ == "__main__":
    ep_install_i = System_Install()
    ep_install_i.welcome()
Ich weiß es entspricht noch nicht den Codestyle, ich gebe mein bestes und lerne Tag für Tag :-)
Ich bin aber auch noch lange nicht fertig :-)

Gruß
BlackJack

@Nexus633: Die `PIPE` machen keinen Sinn solange Du nicht irgendetwas zusätzlich machen musst als den Inhalt einfach nur 1:1 durchzureichen. Die zusätzliche Shell die mit ``shell=True`` gestartet wird macht keinen Sinn solange man nicht irgendetwas ausführen muss was eine Shell benötigt.

Und die Frage wie Du das ausführst hast Du nicht beantwortet. Das ist ein Konsolenprogramm und sollte direkt in einer Konsole ausgeführt werden. Nicht jede IDE biegt `stderr` in eine Fenster um und das könnte dann auf der `stderr`-Ausgabe der IDE landen. Wenn die IDE selbst nicht von einer Konsole gestartet wurde, dann landen solche Ausgaben entweder in einer Logdatei oder im Nirwana.

Sonstige Anmerkungen: Lass Sternchenimporte bleiben. Man holt sich damit alle möglichen Namen in den Namensraum, auch solche die man gar nicht braucht, es besteht die Gefahr von Namenskollisionen, und man kann nicht mehr einfach nachvollziehen aus welchem Modul eigentlich welcher Name kommt.

Die Klassen sind unsinnig. Das sind semantisch keine Klassen sondern einfach nur Sammlungen von Funktionen.

Die Kommentare im Code sind fast alle überflüssig weil sie keinen Mehrwert zum Code bieten. Ausser bei der Sprachauswahl, allerdings braucht man die Kommentare dort auch nur weil der Code nicht so deutlich und verständlich ist wie er sein sollte. Statt magische Zahlen 0 und 1 zu verwenden und im Kommentar zu erklären für welche Sprache die jeweils stehen, würde man Konstanten definieren an denen man das dann auch ohne Kommentar einfach ablesen kann. Und vielleicht sollte man statt der Zahlen besser Zeichenketten nehmen, damit der Wert selber auch Informationen enthält mit denen ein Leser etwas anfangen kann.

``return`` ist keine Funktion, sollte also auch nicht so geschrieben werden als wenn es eine wäre. Da gehören keine Klammern hin.

Funktionen und Methoden werden normalerweise nach Tätigkeiten benannt weil die etwas tun und um sie von anderen Werten zu unterscheiden. Dementsprechend benennt man andere Werte *nicht* nach Tätigkeiten. So etwas wie `say_text` ist deshalb ein Name bei dem die meisten Programmierer eine Funktion erwarten und nicht eine passive Zeichenkette.

Wenn man die Benutzereingabe in Kleinbuchstaben umwandelt, braucht man nicht mehr gegen Worte testen die Grossbuchstaben enthalten. Die *können* ja nicht mehr mit einem Wort in Kleinbuchstaben übereinstimmen. Mengen für solche Tests zu verwenden ist zwar eine nette Idee, allerdings sollte man das dann auch so machen, dass es wenigstens theoretisch effizienter ist als Listen. Wenn man vor jedem Test die Mengen aus Listen erstellt, dann ist das ineffizienter als gleich in einer Liste zu testen. Bei Mengen die nur zwei oder drei Zeichenketten enthalten dürfte eine Menge auch nicht deutlich schneller sein, da würde ich einfach gegen Listen testen.

Lesestoff zu Namensschreibweisen und anderen Konventionen: Style Guide for Python Code.

Eine Funktion die eine Liste liefert viermal aufzurufen nur um dann jedes mal nur ein Element aus dieser Liste an einen Namen zu binden ist unsinnig. Die ruft man *einmal* auf, und bindet die vier Ergebnisse direkt an vier Namen.
Sirius3
User
Beiträge: 17753
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nexus633: Klassen sind kein Instrument um Code zu strukturieren. Programme sollen auch nicht wie Schlangen aussehen (Function welcome ruft Funktion set_language auf, ruft Funktion detect_system auf, usw.), sondern wie ein Baum, wo die oberste Funktion am abstraktesten ist, und mit jeder Stufe konkreter wird.
Das sind mal so Dinge, die völlig unabhängig von der Programmiersprache sind und immer gelten.
Konkret im Code sind auch andere Punkte nicht optimal gelöst:
Sternchenimporte sollte man vermeiden, weil nicht deutlich wird, was da eigentlich alles im eigenen Namensraum landet.
check_os wandelt nicht nur ziemlich umständlich, sinnvolle Namen in nichtssagende Indizes um, das Spiel machst Du weiter unten auch wieder rückgängig, indem Du vier mal die selbe Information ermittelst, um nur ein Element rauszusuchen und den rest wegzuschmeißen. Man macht einen Funktionsaufruf und arbeitet dann mit dem Ergebnis weiter, in diesem Fall würde sich ein NamedTuple anbieten, dann braucht man auch nicht Variablennamen ein- und auspacken.
«return» ist keine Funktion, die Klammern also überflüssig.
Backslash als Zeilenfortsetzung ist deprecated. Man sollte, wenn nötig, Klammern verwenden.
Consolenprogramme, die mir meinen Bildschirm löschen, finde ich ja persönlich bevormundend. Kennst Du nur ein Standard-UNIX-Programm (außer clear) das das macht?
Wenn Du die Abfrage "yes/no" schon in Kleinbuchstaben verwandelst, brauchst Du auch nicht auf alle Kombinationen testen. Sets halte ich auch bei einmaligen nicht zeitkritischen Abfragen für Kanonen auf Spatzen. Auch wird mir viel zu viel an Variablen gebunden, die dann nur ein einziges mal verwendet werden. Das macht den Code nur lang, aber nicht verständlicher.
«set_language» setzt keine Sprache sondern fragt eine ab und gibt sie an die nächste Funktion in der Kaskade weiter, Namen sollten auch das beschreiben, was die Funktion auch tatsächlich tut.
Zeile 67: Du gibst die Repräsentation einer Liste direkt als Benutzerausgabe aus. Das sollte man nicht machen, weil's nicht schön aussieht und nicht gewährleistet ist, dass es bei allen Python-Implementationen gleich aussieht.
Hast Du jetzt vor, alle Texte im Code immer als Liste zweisprachig zu schreiben? Schau Dir mal übliche Lösungen zu i18n an, wie die das machen. Was passiert, wenn Schwedisch als Dritte Sprache dazukommt? Auch würde ich die Nummern bei format durch sprechende Namen ersetzen. Die Backslashy sind hier überflüssig.
Zum Popen-Aufruf hat Blackjack ja schon was geschrieben.
Antworten