Wie subprocess.run() korrekt formulieren?

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
RIN67630
User
Beiträge: 141
Registriert: Sonntag 29. April 2018, 08:07

Hallo Experten hier,

von eine große Logdatei mit ca 90.000 Zeilen muss ich ein Schnipsel (~360 Zeilen) in eine kleinere Datei kopieren.
dazu will ich 2 Linux-Systembefehle von Python aus ausführen, weil die programmierte Suche in Python eine Ewigkeit dauert..

a) awk '/16:17:18/{print NR}' /home/pi/Noise/20-03-10.log

# die Systemantwort ist 23456, sie soll in der Variable fromLine abspeichert werden, dann toLine = fromLine + 360

b) sed -n 23456,23816p, /home/pi/Noise/20-03-10.log > /home/pi/Noise/plot.log

wobei die unterstrichene Bestandsteile Variablen sind:

a) awk '/' + timeStamp + '/{print NR}' + logAdress
b) sed -n fromLine , toLine + 'p' , logAdress +' > ' + plotAdress

Wie kann ich die Python-Befehle formulieren?

ich habe für b) folgendes probiert:

Code: Alles auswählen

runCommand = 'sed -n '+ str(fromLine) + ',' + str(toLine) + 'p ' + logAdress 
print ('running system command: ' + runCommand)
args = shlex.split(runCommand)
print (args)
subprocess.run(args, 'shell = True', 'stdout = ' + plotAdress)
Dabei bekomme ich von /usr/lib/python3.7/subprocess.py line 677 ein "Type error bufsize must be an integer"

Wer kennt die richtige Syntax für die 2 Systemaufrufe und das "Pipen" der Antwort aus a) in fromLine?

Danke vielmals!
Laszlo
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Dann zeig doch mal die programmierte Suche in Python und wir schauen mal, ob man die Ewigkeit eingrenzen kann.
RIN67630
User
Beiträge: 141
Registriert: Sonntag 29. April 2018, 08:07

...kann ich machen, ich denke aber, dass ihr es nich schneller als AWK hinbekommen könnt, oder?:

Code: Alles auswählen

#! /usr/bin/env python3

# getting libraries
import serial
import os, sys
import threading
import queue
from datetime import datetime
import time
import glob
import tkinter as tk
from tkinter import *
from tkinter import ttk
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
from matplotlib.ticker import (MultipleLocator, FormatStrFormatter, AutoMinorLocator)

class Plot(Frame):
    def __init__(self):
        super().__init__() 
        self.initUI()

    def initUI(self):
        # configuration
        self.plot_length = 360
        self.log_path = '/home/pi/Noise/'
        
        # set variables
        self.count = 0
        self.xs = []
        self.y1 = []
        self.y2 = []
        self.y3 = []
        self.y4 = []
        self.y5 = []
        self.start = 1
        self.go = 1
        self.list_lock = 0

        # beautifying plot params
        plt.style.use('seaborn-darkgrid')
        plt.rc('figure', facecolor='darkgrey')
        plt.rc('figure.subplot', left=0.06, right=0.99, top=0.99, bottom=0.08)
        plt.rc('xtick', labelsize=6)     
        plt.rc('ytick', labelsize=6)
        plt.rc('lines', linewidth=1)
        plt.rc('legend', frameon=True, loc='upper left', facecolor='white', framealpha=0.5, fontsize=7)
        plt.rc('font', size=9)
        plt.ioff()
        #kill the useless Matplotlib toolbar
        plt.rcParams['toolbar'] = 'None'


        Frame10 = tk.Frame(width=300, height=100)
        Frame10.grid_propagate(0)
        Frame10.grid(row=0, column=0)
        button2 = tk.Button(Frame10, text="exit", command=self.exit, bg = "red")
        button2.grid(row = 0, column = 3)
        button = tk.Button(Frame10, text="Plot", command=self.Plot, bg = "green", fg = "white")
        button.grid(row = 0, column = 0)
        button3 = tk.Button(Frame10, text="Clear", command=self.clear_data, bg = "gray", fg = "white")
        button3.grid(row = 0, column = 1, padx = 50)
        self.in_date = tk.Text(Frame10, height=1, width=8)
        self.in_date.grid(row = 1, column = 1)
        self.starttime = tk.Text(Frame10, height=1, width=8)
        self.starttime.grid(row = 2, column = 1)
        self.endtime = tk.Text(Frame10, height=1, width=8)
        self.endtime.grid(row = 3, column = 1)
        L1 = tk.Label(Frame10, text="Date :" , font = 20)
        L1.place(x=2, y=30)
        L2 = tk.Label(Frame10, text="Start Time :" , font = 20)
        L2.place(x=0, y=50)
        L3 = tk.Label(Frame10, text="  End Time :" , font = 20)
        L3.place(x=0, y=70)
        self.L4 = tk.Label(Frame10, text="" , font = 20)
        self.L4.place(x=180, y=30)


    def clear_data(self):
        self.in_date.delete('1.0','30.0')
        self.starttime.delete('1.0','30.0')
        self.endtime.delete('1.0','30.0')

    def exit(self):
        self.start = 0
        self.master.destroy()
        
    def exit2(self,x):
        self.start = 0
        self.master.destroy()

    def Plot(self):
        self.start = 0
        self.log_file = self.log_path + str(self.in_date.get('1.0','8.0'))[0:8] + ".log"
        if len(self.starttime.get('1.0','8.0')) > 2 and len(self.endtime.get('1.0','8.0')) > 2:
            self.start_time = str(self.starttime.get('1.0','8.0'))[0:8]
            self.end_time = str(self.endtime.get('1.0','8.0'))[0:8]
        else:
            self.start_time = "00:00:01"
            self.end_time = "23:59:59"
        self.read_plot()
    
    def thread_plot(self):
        self.fig = plt.figure("Sound Pressure Level")
        self.ax1 = self.fig.add_subplot(1,1,1)
        self.cid = self.fig.canvas.mpl_connect('button_press_event', self.exit2)
        self.ani = animation.FuncAnimation(self.fig, self.animate, interval=1000) #update every 1 sec
        plt.show()
   
    def animate(self,i):
        if self.count > 0 and self.start == 1:
          self.ax1.clear()
          plt.xlabel('Log: ' + self.log_file + '     -     click on the plot to exit') 
          plt.ylabel('dB')
          self.ax1.xaxis.set_major_locator(MultipleLocator(30))
          self.ax1.xaxis.set_minor_locator(MultipleLocator(10))
          while self.list_lock == 1:
             time.sleep(0.01)
          self.list_lock = 1
          self.ax1.plot(self.xs, self.y1, '-.b', label='Avg')     # dash-dotted line, blue
          self.ax1.plot(self.xs, self.y2, '-r',  label='A0')      # solid line, red
          self.ax1.plot(self.xs, self.y3, '-g',  label='A0Slow')  # solid line, green
          self.ax1.plot(self.xs, self.y4, ':c',  label='Min')     # dotted line, cyan
          self.ax1.plot(self.xs, self.y5, ':y',  label='Max')     # dotted line, yellow
          self.ax1.legend(loc='upper left')
          self.list_lock = 0
        if self.count > 0 and self.start == 0 :
            plt.close('all')
          

    def read_plot(self):
      if os.path.exists (self.log_file):
         self.L4.config(text= "      ")
         self.xs = []
         self.y1 = []
         self.y2 = []
         self.y3 = []
         self.y4 = []
         self.y5 = []
         self.start = 1
         self.go = 1

         if self.count == 0:
             plot_thread = threading.Thread(target=self.thread_plot)
             plot_thread.start()
         self.count = 1
         while self.go == 1:
          #read log
           log= []
        
           with open(self.log_file, "r") as file:
              line = file.readline()
              log.append(line)
              while line:
                  line = file.readline()
                  log.append(line)
                  
           for x in range(0,len(log)):
              # set read delay
              time.sleep(.01)
              # check for data lines
              if log[x][0:1] == "d":
                  counter1 = log[x].count(' ')
                  counter2 = log[x].count('.')
                  if counter1 == 6 and counter2 == 5:     # valid data line
                    a,t,b,c,d,e,f = log[x].split(" ")
                    time1 = datetime.strptime(t, '%H:%M:%S').time()
                    time2 = datetime.strptime(self.start_time, '%H:%M:%S').time()
                    time3 = datetime.strptime(self.end_time, '%H:%M:%S').time()
                    if time1 >= time2 and time1 <= time3:
                      while self.list_lock == 1:
                          time.sleep(0.01)
                      self.list_lock = 1
                      self.xs.append(t)
                      self.y1.append(float(b))
                      self.y2.append(float(c))
                      self.y3.append(float(d))
                      self.y4.append(float(e))
                      self.y5.append(float(f))
                      # delete old list values
                      if len(self.xs) > self.plot_length:
                          del self.xs[0]
                          del self.y1[0]
                          del self.y2[0]
                          del self.y3[0]
                          del self.y4[0]
                          del self.y5[0]
                      self.list_lock = 0
 
           self.go = 0
      else:
           self.go = 0
           print ("Log file not found")
           self.L4.config(text= "No Log")
           self.in_date.delete('1.0','30.0')

          
def main():
    root = Tk()
    root.title("Choose a log...")
    ex = Plot()
    root.geometry("300x100")
    root.mainloop() 

if __name__ == '__main__':
    main() 

und hier die Logdatei als Quelle:
https://www.cjoint.com/c/JCmrBtJBWj1
bitte im Verzeichnis /home/pi/Noise/ abspeichern.

Bei mir auf einem RPi 3b+ dauert die Python Suche 8 Minuten
AWK macht es sofort...

P.S. die Uhrzeit als Timestamp in der Datei gibt es nur einmal, man kann auch unstrukturiert suchen...
Es is sicher schneller erstmals die Startzeile zu suchen, dann den Block kopieren und von der kleinere Datei den Plot zu generieren.
Die Logdate kann (selten) Lücken haben, warnung reicht...
Zuletzt geändert von RIN67630 am Freitag 13. März 2020, 15:38, insgesamt 1-mal geändert.
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@RIN67630: In der Python-Dokumentation gibt es ein Tutorial wo Funktionsaufrufe erklärt sind und wie man Argumente übergibt. Das was Du da machst ist ja kein `subprocess.run()`-spezifisches Problem, Du machst ja grundsätzlich den Aufruf von Schlüsselargumenten falsch. Und wenn man es richtig machen würde sind beide Argumente falsch. Du willst keine zusätzliche Shell die keinen Sinn macht und `stdout` will eine der speziellen Konstanten oder ein Dateiobjekt und keine Zeichenkette mit einem Dateinamen.

Es macht auch keinen Sinn erst eine Zeichenkette mit einem Befehl aufzubauen und den dann mit `shlex.split()` in einzelne Bestandteile zu zerlegen. Was so ja auch nur funktioniert wenn keine der Variablen die Du das mit `str()` und ``+`` zusammenstückelst etwas enthält was man für eine Shell speziell schützen müsste. Das ist als nicht nur unnötig und umständlich sondern auch noch zusätzlich fehleranfällig.

Zeichenketten mit `str()` und ``+`` zusammenstückeln ist auch eher BASIC als Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode und ab Python 3.6 f-Zeichenkettenliterale.

Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Dateinamen sind eher keine ”Adressen”. `log_address` und `plot_address` sind keine guten Namen dafür.

Ansonsten schliesse ich mit sparrow an: wenn das in Python ”ewig” braucht ist es falsch programmiert. Trenn doch bitte mal das einlesen in eine eigene Funktion auf die auch kleine Schleifen mit `time.sleep()`-Aufrufen enthält. Du machst das mit solchen Aufrufen *selbst* künstlich langsamer und beschwerst Dich dann das es zu langsam sei. Zudem sehe ich da nicht dass das das gleiche macht wie die beiden externen Aufrufe. Wenn Du sagst Python sei zu langsam dann programmiere und messe doch erst einmal die Laufzeit von etwas das tatsächlich das gleiche macht.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
RIN67630
User
Beiträge: 141
Registriert: Sonntag 29. April 2018, 08:07

__blackjack__ hat geschrieben: Freitag 13. März 2020, 15:35 @RIN67630: In der Python-Dokumentation gibt es ein Tutorial wo Funktionsaufrufe erklärt sind und wie man Argumente übergibt. Das was Du da machst ist ja kein `subprocess.run()`-spezifisches Problem, Du machst ja grundsätzlich den Aufruf von Schlüsselargumenten falsch. Und wenn man es richtig machen würde sind beide Argumente falsch. Du willst keine zusätzliche Shell die keinen Sinn macht und `stdout` will eine der speziellen Konstanten oder ein Dateiobjekt und keine Zeichenkette mit einem Dateinamen.

Es macht auch keinen Sinn erst eine Zeichenkette mit einem Befehl aufzubauen und den dann mit `shlex.split()` in einzelne Bestandteile zu zerlegen. Was so ja auch nur funktioniert wenn keine der Variablen die Du das mit `str()` und ``+`` zusammenstückelst etwas enthält was man für eine Shell speziell schützen müsste. Das ist als nicht nur unnötig und umständlich sondern auch noch zusätzlich fehleranfällig.

Zeichenketten mit `str()` und ``+`` zusammenstückeln ist auch eher BASIC als Python. In Python gibt es dafür Zeichenkettenformatierung mit der `format()`-Methode und ab Python 3.6 f-Zeichenkettenliterale.

Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Dateinamen sind eher keine ”Adressen”. `log_address` und `plot_address` sind keine guten Namen dafür.

Ansonsten schliesse ich mit sparrow an: wenn das in Python ”ewig” braucht ist es falsch programmiert. Trenn doch bitte mal das einlesen in eine eigene Funktion auf die auch kleine Schleifen mit `time.sleep()`-Aufrufen enthält. Du machst das mit solchen Aufrufen *selbst* künstlich langsamer und beschwerst Dich dann das es zu langsam sei. Zudem sehe ich da nicht dass das das gleiche macht wie die beiden externen Aufrufe. Wenn Du sagst Python sei zu langsam dann programmiere und messe doch erst einmal die Laufzeit von etwas das tatsächlich das gleiche macht.
Ja ich habe mittlerweile die Vorgehensweise angepasst: zuerst den Schnipsel finden, kopieren und dann plotten.
Wie schnell kann Python den timestamp in der Datei finden und wie am schnellsten?
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@RIN67630: Über „am schnellsten“ würde ich mir gar keine Gedanken machen. Wenn man das ganz normal einfach programmiert ist das schnell genug. Beim Raspi wenn die Daten nicht schon im Cache liegen dürfte das lesen/schreiben der Daten die eigentliche Suche deutlich in den Schatten stellen, egal wie man das programmiert (ausser man macht es so dass es 8 Minuten dauert).
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
RIN67630
User
Beiträge: 141
Registriert: Sonntag 29. April 2018, 08:07

__blackjack__ hat geschrieben: Freitag 13. März 2020, 16:02 @RIN67630: Über „am schnellsten“ würde ich mir gar keine Gedanken machen. Wenn man das ganz normal einfach programmiert ist das schnell genug. Beim Raspi wenn die Daten nicht schon im Cache liegen dürfte das lesen/schreiben der Daten die eigentliche Suche deutlich in den Schatten stellen, egal wie man das programmiert (ausser man macht es so dass es 8 Minuten dauert).
und wie programmiert man das finden der Zeilennummer eines bestimmten Inhaltes?
wie programmiert man das gezieltes Einlesen von bestimmten Zeilenummern?
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Du musst die Zeilennummer gar nicht finden.
Du iterierst durch die Zeilen einer Datei. Wenn die Zeile das enhält, was du suchst, läufst du einfach weiter, merkst dir nun aber die Zeilen (oder schreibst sie direkt weg).
Und nach x darauffolgenden Zeilen, brichst du den Durchlauf ab.

Und wenn du mehrmals die gleiche Datei für verschiedene Inhalte lesen musst, dann kann man es sogar so geschickt anstellen, dass man durch die Datei nur ein einziges Mal durchläuft und dabei gleich alle Aufgaben abarbeitet.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

@RIN67630:
Ein paar Anmerkungen zu deinem Code:

serial wird importiert aber nicht verwendet.

*-Importe sind böse.
Und du importiest

Code: Alles auswählen

import tkinter as tk
from tkinter import *
Also einmal als tk und einmal alles in den Hauptnamensraum. Ersteres ist richtig, zweiteres ist falsch und doppelt.

Das Durchnummerieren von Dinen ist selten sinnvoll.
Richtig sinnlos ist es, wenn die Buttons button heißen und durchnummeriert sind. Ich weiß dann nicht, welcher Button was ist.

Zeichenketten stückelt man niemals mit + zusammen.
Benutze f-Strings.

Was ist denn exit2? Identisch zu exit1, nur dass ein weitees Argument übergeben wird, das aber nicht verwendet wird?

Das was du da mit dem Thread machst, sieht falsch aus.
Außerdem greifst du aus dem Thread auf die GUI zu. Wenn es hier nicht eine große Ausnahme für matplotlib gibt, dann ist das falsch und du musst aus dem Thread heraus über eine Queue mit der Hauptschleife kommunizieren.

Das Programm ist völlig konfus.
Konstanten, wie der Pfad zum Log-Verzeichnis, gehören als Konstanten auf Modulebene und nicht irgenwo im Code versteckt.

Dein Log-File in das Verzeichnis zu kopieren nützt nichts. Er findet es trotzdem nicht. Ich habe aber keine Lust deine Handhabung da zu debuggen. Und Pfade stückelt man erst recht nicht mit + zusammen. Man nimmt pathlib.

Das was du mit awk und sed da oben zeigst, funktioniert übrigens bei der von dir gezeigten Log-Datei so schnell, dass sich zumindest auf dem System hier nicht einmal das timen lohnt, weil der Interpreter wahrscheinlich länger zum initialisieren braucht, wie die Zeilen zu filtern. Was du dafür machen musst, habe ich ja oben schon geschrieben.
RIN67630
User
Beiträge: 141
Registriert: Sonntag 29. April 2018, 08:07

Hallo Sparrow,
Das Programm ist nicht von mir.
Ich hätte das nicht objektorientiert geschrieben und ohne tkinter.
Ein paar Dialogzeilen im Terminal am anfang und gut wär's gewesen...
Bin noch sehr "basic" und eher in C++ (Arduino) zu Hause.
AWK brauch ich jetzt nicht mehr, da habt Ihr Recht, die Zeilen abzufragen kostet kaum was.
Ich bin zur Zeit dabei das in diesem Stil umzuschreiben:

Code: Alles auswählen

objFile = open(log_path_file, "r")
x = 0
take = 1
for line in objFile:
    if  start_time in line:
       print (x)
       from_line = x
       take = 1
    if stop_time in line:
       print (x)
       to_line = x
       take = 0

    if take:
        pass
        # here we could probably continue directly with 
        # Ard_data = line
        # Ard_data = Ard_data.decode("utf-8","ignore")
        #  counter1 = Ard_data.count(' ')
        #  counter2 = Ard_data.count('.')
             # check for 6 spaces  and 5 data values
             # and so forth...
    x += 1       
    
Dann noch ein bischen Ausnahmenhandling und es müsste fliegen.
Aber ich bin in Python sehr langsam, da muss ich mir noch viel aneignen...
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@RIN67630: Es gibt ``elif``. Und statt `x` – was einen besseren Namen vertragen könnte – sollte man mit `enumerate()` erzeugen statt es manuell über den Code verteilt selbst hochzuzählen.

Was soll der Name `objFile` bedeuten? Falls das `obj` andeuten soll, dass es sich um ein Objekt handelt: *alles* was man in Python an einen Namen binden kann ist ein Objekt. Dann müsste es auch `objX`, `objLine`, `objStart_time`, … beziehungsweise `obj_file`, `obj_x`, `obj_line`, … wenn man sich an die Namenskonventionen hält. Wenn man aber überall ein `obj` davor pappt, kann man es auch überall lassen. Das bringt einfach keine zusätzliche Information.

Dateien sollte man wo es geht mit der ``with``-Anweisung zusammen öffnen.

Python hat einen Datentyp für Wahrheitswerte, da sollte man nicht 0 und 1 für missbrauchen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
RIN67630
User
Beiträge: 141
Registriert: Sonntag 29. April 2018, 08:07

__blackjack__ hat geschrieben: Freitag 13. März 2020, 22:46 @RIN67630: Es gibt ``elif``. Und statt `x` – was einen besseren Namen vertragen könnte – sollte man mit `enumerate()` erzeugen statt es manuell über den Code verteilt selbst hochzuzählen.

Was soll der Name `objFile` bedeuten? Falls das `obj` andeuten soll, dass es sich um ein Objekt handelt: *alles* was man in Python an einen Namen binden kann ist ein Objekt. Dann müsste es auch `objX`, `objLine`, `objStart_time`, … beziehungsweise `obj_file`, `obj_x`, `obj_line`, … wenn man sich an die Namenskonventionen hält. Wenn man aber überall ein `obj` davor pappt, kann man es auch überall lassen. Das bringt einfach keine zusätzliche Information.

Dateien sollte man wo es geht mit der ``with``-Anweisung zusammen öffnen.

Python hat einen Datentyp für Wahrheitswerte, da sollte man nicht 0 und 1 für missbrauchen.
Danke für die Tipps.
Das Verdauen von Tausende Konventionen dauert noch etwas. Ist im Moment nicht mein Fokus. Ich bin erstmals mit der Grundfunktion beschäftigt.
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Dann wirst du ein Pronkem kriegen.
"Mir geht es ersznsl nur darum, dass es läuft" und sämtliche Tipps in den Wind zu schlagen, führt halt zu einen unrubusten und schlechten Programm. Dann kannst du dir die Lebenszeit auch sparen.

Zwischen Funktionsnamen ind ( kommt kein Leerzeicjen.

Dateien öffnet man mit dem with statement, damit man sich nicht ums Schließen kümmern muss.
Benutzeravatar
noisefloor
User
Beiträge: 4193
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Das Verdauen von Tausende Konventionen dauert noch etwas. Ist im Moment nicht mein Fokus.
Die Einstellung ist: falsch.

Die PEP8, wo die ganzen Konventionen drin stehen, ist der heilige Gral der Python Programmierung. Und zumindest du Grundregeln davon umzusetzen wie Konventionen zur Schreibweise von Variablennamen, Einrückung mit vier Leerzeichen und ein paar andere Sachen sind integraler Bestandteil von Python bzw. eines Python Skripts.
Sonst bekommst du das immer und immer wieder um die Ohren gehauen, bevor irgendwer überhaupt irgendwas zu deinem Code sagt.

Also: direkt umsetzen, zumal das auch nicht wirklich schwer oder aufwendig ist. Hilft aber ungemein.

Gruß, noisefloor
RIN67630
User
Beiträge: 141
Registriert: Sonntag 29. April 2018, 08:07

noisefloor hat geschrieben: Samstag 14. März 2020, 09:16 Hallo,
Das Verdauen von Tausende Konventionen dauert noch etwas. Ist im Moment nicht mein Fokus.
Die Einstellung ist: falsch.
Bild
Antworten