Teilen Sie eine Zeichenfolge in Python durch Leerzeichen, wobei Anführungszeichen in Anführungszeichen beibehalten werden


267

Ich habe eine Zeichenfolge, die wie folgt aussieht:

this is "a test"

Ich versuche, etwas in Python zu schreiben, um es nach Leerzeichen aufzuteilen, während Leerzeichen in Anführungszeichen ignoriert werden. Das gesuchte Ergebnis ist:

['this','is','a test']

PS. Ich weiß, dass Sie fragen werden: "Was passiert, wenn die Anführungszeichen Anführungszeichen enthalten? Nun, in meiner Bewerbung wird das niemals passieren."


1
Vielen Dank, dass Sie diese Frage gestellt haben. Es ist genau das, was ich zum Reparieren des Pypar-Build-Moduls brauchte.
Martlark

Antworten:


390

Sie möchten split, aus dem eingebauten shlexModul.

>>> import shlex
>>> shlex.split('this is "a test"')
['this', 'is', 'a test']

Dies sollte genau das tun, was Sie wollen.


13
Verwenden Sie "posix = False", um Zitate beizubehalten. shlex.split('this is "a test"', posix=False)kehrt zurück['this', 'is', '"a test"']
Boon

@ MatthewG. Das "Update" in Python 2.7.3 bedeutet, dass das Übergeben einer Unicode-Zeichenfolge an shlex.split()eine UnicodeEncodeErrorAusnahme auslöst .
Rockallite

56

Schauen Sie sich shlexinsbesondere das Modul an shlex.split.

>>> import shlex
>>> shlex.split('This is "a test"')
['This', 'is', 'a test']

39

Ich sehe hier Regex-Ansätze, die komplex und / oder falsch aussehen. Dies überrascht mich, da die Regex-Syntax leicht "Leerzeichen oder von Anführungszeichen umgebene Dinge" beschreiben kann und die meisten Regex-Engines (einschließlich Pythons) in einen Regex aufgeteilt werden können. Wenn Sie also reguläre Ausdrücke verwenden möchten, sagen Sie doch einfach genau, was Sie meinen.:

test = 'this is "a test"'  # or "this is 'a test'"
# pieces = [p for p in re.split("( |[\\\"'].*[\\\"'])", test) if p.strip()]
# From comments, use this:
pieces = [p for p in re.split("( |\\\".*?\\\"|'.*?')", test) if p.strip()]

Erläuterung:

[\\\"'] = double-quote or single-quote
.* = anything
( |X) = space or X
.strip() = remove space and empty-string separators

shlex bietet jedoch wahrscheinlich mehr Funktionen.


1
Ich dachte ähnlich, würde aber stattdessen [t.strip ('"') für t in re.findall (r '[^ \ s"] + | "[^"] * "' vorschlagen, 'this is" ein Test "')]
Darius Bacon

2
+1 Ich benutze das, weil es verdammt viel schneller war als Shlex.
Hanleyp

3
Warum der dreifache Backslash? Wird ein einfacher Backslash nicht dasselbe tun?
Doppelgänger

1
Eine Sache, die ich nicht mag, ist, dass alles vor / nach Anführungszeichen nicht richtig aufgeteilt wird. Wenn ich eine Zeichenfolge wie diese habe, 'PARAMS val1 = "Thing" val2 = "Thing2"'. Ich erwarte, dass sich die Zeichenfolge in drei Teile aufteilt, aber sie teilt sich in fünf Teile auf. Es ist eine Weile her, seit ich Regex gemacht habe, daher habe ich keine Lust, sie jetzt mit Ihrer Lösung zu lösen.
leetNightshade

1
Sie sollten rohe Zeichenfolgen verwenden, wenn Sie reguläre Ausdrücke verwenden.
Asmeurer

28

Abhängig von Ihrem Anwendungsfall möchten Sie möglicherweise auch das csvModul überprüfen :

import csv
lines = ['this is "a string"', 'and more "stuff"']
for row in csv.reader(lines, delimiter=" "):
    print(row)

Ausgabe:

['this', 'is', 'a string']
['and', 'more', 'stuff']

2
nützlich, wenn Shlex einige benötigte Zeichen entfernt
Scraplesh

1
CSVs verwenden zwei doppelte Anführungszeichen in einer Reihe (wie nebeneinander ""), um ein doppeltes Anführungszeichen darzustellen. Daher "werden zwei doppelte Anführungszeichen in ein einfaches Anführungszeichen umgewandelt 'this is "a string""'und 'this is "a string"""'beide werden['this', 'is', 'a string"']
Boris

15

Ich verwende shlex.split, um 70.000.000 Zeilen Tintenfischprotokoll zu verarbeiten. Es ist so langsam. Also wechselte ich zu re.

Bitte versuchen Sie dies, wenn Sie Leistungsprobleme mit Shlex haben.

import re

def line_split(line):
    return re.findall(r'[^"\s]\S*|".+?"', line)

8

Da diese Frage mit Regex gekennzeichnet ist, habe ich mich für einen Regex-Ansatz entschieden. Ich ersetze zuerst alle Leerzeichen in den Anführungszeichen durch \ x00, teile sie dann durch Leerzeichen und ersetze dann die \ x00 zurück durch Leerzeichen in jedem Teil.

Beide Versionen machen dasselbe, aber Splitter ist etwas besser lesbar als Splitter2.

import re

s = 'this is "a test" some text "another test"'

def splitter(s):
    def replacer(m):
        return m.group(0).replace(" ", "\x00")
    parts = re.sub('".+?"', replacer, s).split()
    parts = [p.replace("\x00", " ") for p in parts]
    return parts

def splitter2(s):
    return [p.replace("\x00", " ") for p in re.sub('".+?"', lambda m: m.group(0).replace(" ", "\x00"), s).split()]

print splitter2(s)

Sie sollten stattdessen re.Scanner verwenden. Es ist zuverlässiger (und ich habe tatsächlich einen Shlex-ähnlichen mit re.Scanner implementiert).
Devin Jeanpierre

+1 Hm, das ist eine ziemlich kluge Idee, die das Problem in mehrere Schritte unterteilt, sodass die Antwort nicht besonders komplex ist. Shlex hat nicht genau das getan, was ich brauchte, selbst wenn er versucht hat, es zu optimieren. Und die Single-Pass-Regex-Lösungen wurden wirklich seltsam und kompliziert.
leetNightshade

6

Es scheint, dass aus Leistungsgründen reschneller ist. Hier ist meine Lösung mit einem am wenigsten gierigen Operator, der die äußeren Anführungszeichen beibehält:

re.findall("(?:\".*?\"|\S)+", s)

Ergebnis:

['this', 'is', '"a test"']

Es hinterlässt Konstrukte wie aaa"bla blub"bbbzusammen, da diese Token nicht durch Leerzeichen getrennt sind. Wenn die Zeichenfolge maskierte Zeichen enthält, können Sie wie folgt übereinstimmen:

>>> a = "She said \"He said, \\\"My name is Mark.\\\"\""
>>> a
'She said "He said, \\"My name is Mark.\\""'
>>> for i in re.findall("(?:\".*?[^\\\\]\"|\S)+", a): print(i)
...
She
said
"He said, \"My name is Mark.\""

Bitte beachten Sie, dass dies auch mit der leeren Zeichenfolge ""über den \STeil des Musters übereinstimmt .


1
Ein weiterer wichtiger Vorteil dieser Lösung ist ihre Vielseitigkeit hinsichtlich des Begrenzungscharakters (zB ,via '(?:".*?"|[^,])+'). Gleiches gilt für die Anführungszeichen.
a_guest

4

Das Hauptproblem bei dem akzeptierten shlexAnsatz besteht darin, dass Escape-Zeichen außerhalb von Anführungszeichen in Anführungszeichen nicht ignoriert werden und in einigen Eckfällen leicht unerwartete Ergebnisse erzielt werden.

Ich habe den folgenden Anwendungsfall, in dem ich eine Teilungsfunktion benötige, die Eingabezeichenfolgen so aufteilt, dass entweder einfache oder doppelte Anführungszeichen erhalten bleiben, wobei Anführungszeichen innerhalb einer solchen Teilzeichenfolge vermieden werden können. Anführungszeichen in einer Zeichenfolge ohne Anführungszeichen sollten nicht anders behandelt werden als andere Zeichen. Einige Beispieltestfälle mit der erwarteten Ausgabe:

Eingabezeichenfolge | erwartete Ausgabe
===============================================
 'abc def' | ['abc', 'def']
 "abc \\ s def" | ['abc', '\\ s', 'def']
 '"abc def" ghi' | ['abc def', 'ghi']
 "'abc def' ghi" | ['abc def', 'ghi']
 '"abc \\" def "ghi' | ['abc" def', 'ghi']
 "'abc \\' def 'ghi" | ["abc 'def",' ghi ']
 "'abc \\ s def' ghi" | ['abc \\ s def', 'ghi']
 '"abc \\ s def" ghi' | ['abc \\ s def', 'ghi']
 '"" test' | ['', 'Prüfung']
 "'' test" | ['', 'Prüfung']
 "abc'def" | ["abc'def"]
 "abc'def '" | ["abc'def '"]
 "abc'def 'ghi" | ["abc'def '",' ghi ']
 "abc'def'ghi" | ["abc'def'ghi"]
 'abc "def' | ['abc" def']
 'abc "def"' | ['abc "def"']
 'abc "def" ghi' | ['abc "def"', 'ghi']
 'abc "def" ghi' | ['abc "def" ghi']
 "r'AA 'r'. * _ xyz $ '" | ["r'AA '", "r'. * _ xyz $ '"]

Am Ende hatte ich die folgende Funktion, um eine Zeichenfolge so aufzuteilen, dass die erwartete Ausgabe für alle Eingabezeichenfolgen resultiert:

import re

def quoted_split(s):
    def strip_quotes(s):
        if s and (s[0] == '"' or s[0] == "'") and s[0] == s[-1]:
            return s[1:-1]
        return s
    return [strip_quotes(p).replace('\\"', '"').replace("\\'", "'") \
            for p in re.findall(r'"(?:\\.|[^"])*"|\'(?:\\.|[^\'])*\'|[^\s]+', s)]

Die folgende Testanwendung überprüft die Ergebnisse anderer Ansätze ( shlexund csvvorerst) und der benutzerdefinierten Split-Implementierung:

#!/bin/python2.7

import csv
import re
import shlex

from timeit import timeit

def test_case(fn, s, expected):
    try:
        if fn(s) == expected:
            print '[ OK ] %s -> %s' % (s, fn(s))
        else:
            print '[FAIL] %s -> %s' % (s, fn(s))
    except Exception as e:
        print '[FAIL] %s -> exception: %s' % (s, e)

def test_case_no_output(fn, s, expected):
    try:
        fn(s)
    except:
        pass

def test_split(fn, test_case_fn=test_case):
    test_case_fn(fn, 'abc def', ['abc', 'def'])
    test_case_fn(fn, "abc \\s def", ['abc', '\\s', 'def'])
    test_case_fn(fn, '"abc def" ghi', ['abc def', 'ghi'])
    test_case_fn(fn, "'abc def' ghi", ['abc def', 'ghi'])
    test_case_fn(fn, '"abc \\" def" ghi', ['abc " def', 'ghi'])
    test_case_fn(fn, "'abc \\' def' ghi", ["abc ' def", 'ghi'])
    test_case_fn(fn, "'abc \\s def' ghi", ['abc \\s def', 'ghi'])
    test_case_fn(fn, '"abc \\s def" ghi', ['abc \\s def', 'ghi'])
    test_case_fn(fn, '"" test', ['', 'test'])
    test_case_fn(fn, "'' test", ['', 'test'])
    test_case_fn(fn, "abc'def", ["abc'def"])
    test_case_fn(fn, "abc'def'", ["abc'def'"])
    test_case_fn(fn, "abc'def' ghi", ["abc'def'", 'ghi'])
    test_case_fn(fn, "abc'def'ghi", ["abc'def'ghi"])
    test_case_fn(fn, 'abc"def', ['abc"def'])
    test_case_fn(fn, 'abc"def"', ['abc"def"'])
    test_case_fn(fn, 'abc"def" ghi', ['abc"def"', 'ghi'])
    test_case_fn(fn, 'abc"def"ghi', ['abc"def"ghi'])
    test_case_fn(fn, "r'AA' r'.*_xyz$'", ["r'AA'", "r'.*_xyz$'"])

def csv_split(s):
    return list(csv.reader([s], delimiter=' '))[0]

def re_split(s):
    def strip_quotes(s):
        if s and (s[0] == '"' or s[0] == "'") and s[0] == s[-1]:
            return s[1:-1]
        return s
    return [strip_quotes(p).replace('\\"', '"').replace("\\'", "'") for p in re.findall(r'"(?:\\.|[^"])*"|\'(?:\\.|[^\'])*\'|[^\s]+', s)]

if __name__ == '__main__':
    print 'shlex\n'
    test_split(shlex.split)
    print

    print 'csv\n'
    test_split(csv_split)
    print

    print 're\n'
    test_split(re_split)
    print

    iterations = 100
    setup = 'from __main__ import test_split, test_case_no_output, csv_split, re_split\nimport shlex, re'
    def benchmark(method, code):
        print '%s: %.3fms per iteration' % (method, (1000 * timeit(code, setup=setup, number=iterations) / iterations))
    benchmark('shlex', 'test_split(shlex.split, test_case_no_output)')
    benchmark('csv', 'test_split(csv_split, test_case_no_output)')
    benchmark('re', 'test_split(re_split, test_case_no_output)')

Ausgabe:

Shlex

[OK] abc def -> ['abc', 'def']
[FAIL] abc \ s def -> ['abc', 's', 'def']
[OK] "abc def" ghi -> ['abc def', 'ghi']
[OK] 'abc def' ghi -> ['abc def', 'ghi']
[OK] "abc \" def "ghi -> ['abc" def', 'ghi']
[FAIL] 'abc \' def 'ghi -> Ausnahme: Kein abschließendes Zitat
[OK] 'abc \ s def' ghi -> ['abc \\ s def', 'ghi']
[OK] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[OK] "" test -> ['', 'test']
[OK] '' test -> ['', 'test']
[FAIL] abc'def -> Ausnahme: Kein abschließendes Zitat
[FAIL] abc'def '-> [' abcdef ']
[FAIL] abc'def 'ghi -> [' abcdef ',' ghi ']
[FAIL] abc'def'ghi -> ['abcdefghi']
[FAIL] abc "def -> Ausnahme: Kein abschließendes Angebot
[FAIL] abc "def" -> ['abcdef']
[FAIL] abc "def" ghi -> ['abcdef', 'ghi']
[FAIL] abc "def" ghi -> ['abcdefghi']
[FAIL] r'AA 'r'. * _ Xyz $ '-> [' rAA ',' r. * _ Xyz $ ']

csv

[OK] abc def -> ['abc', 'def']
[OK] abc \ s def -> ['abc', '\\ s', 'def']
[OK] "abc def" ghi -> ['abc def', 'ghi']
[FAIL] 'abc def' ghi -> ["'abc", "def'", 'ghi']
[FAIL] "abc \" def "ghi -> ['abc \\', 'def"', 'ghi']
[FAIL] 'abc \' def 'ghi -> ["' abc", "\\ '", "def'", 'ghi']
[FAIL] 'abc \ s def' ghi -> ["'abc",' \\ s ', "def'", 'ghi']
[OK] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[OK] "" test -> ['', 'test']
[FAIL] '' test -> ["''", 'test']
[OK] abc'def -> ["abc'def"]
[OK] abc'def '-> ["abc'def'"]
[OK] abc'def 'ghi -> ["abc'def'", 'ghi']
[OK] abc'def'ghi -> ["abc'def'ghi"]
[OK] abc "def -> ['abc" def']
[OK] abc "def" -> ['abc "def"']
[OK] abc "def" ghi -> ['abc "def"', 'ghi']
[OK] abc "def" ghi -> ['abc "def" ghi']
[OK] r'AA 'r'. * _ Xyz $ '-> ["r'AA'", "r '. * _ Xyz $'"]

Re

[OK] abc def -> ['abc', 'def']
[OK] abc \ s def -> ['abc', '\\ s', 'def']
[OK] "abc def" ghi -> ['abc def', 'ghi']
[OK] 'abc def' ghi -> ['abc def', 'ghi']
[OK] "abc \" def "ghi -> ['abc" def', 'ghi']
[OK] 'abc \' def 'ghi -> ["abc' def", 'ghi']
[OK] 'abc \ s def' ghi -> ['abc \\ s def', 'ghi']
[OK] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[OK] "" test -> ['', 'test']
[OK] '' test -> ['', 'test']
[OK] abc'def -> ["abc'def"]
[OK] abc'def '-> ["abc'def'"]
[OK] abc'def 'ghi -> ["abc'def'", 'ghi']
[OK] abc'def'ghi -> ["abc'def'ghi"]
[OK] abc "def -> ['abc" def']
[OK] abc "def" -> ['abc "def"']
[OK] abc "def" ghi -> ['abc "def"', 'ghi']
[OK] abc "def" ghi -> ['abc "def" ghi']
[OK] r'AA 'r'. * _ Xyz $ '-> ["r'AA'", "r '. * _ Xyz $'"]

Shlex: 0,281 ms pro Iteration
csv: 0,030 ms pro Iteration
re: 0,049 ms pro Iteration

Die Leistung ist also viel besser als shlexund kann durch Vorkompilieren des regulären Ausdrucks weiter verbessert werden. In diesem Fall wird der csvAnsatz übertroffen .


Ich bin mir nicht sicher, wovon du sprichst: `` >>> shlex.split ('das ist "ein Test"') ['das', 'ist', 'ein Test'] >>> shlex.split (' Dies ist "ein Test") ["dies", "ist", "ein", "Test"] >>> shlex.split ("dies ist" ein "Test") "') [' this ',' is ',' a" test "']` ``
morsik

@morsik, was ist dein Punkt? Vielleicht passt Ihr Anwendungsfall nicht zu meinem? Wenn Sie sich die Testfälle ansehen, sehen Sie alle Fälle, in denen shlexsich meine Anwendungsfälle nicht wie erwartet verhalten.
Ton van den Heuvel

3

Verwenden Sie diese Funktion, um Anführungszeichen beizubehalten:

def getArgs(s):
    args = []
    cur = ''
    inQuotes = 0
    for char in s.strip():
        if char == ' ' and not inQuotes:
            args.append(cur)
            cur = ''
        elif char == '"' and not inQuotes:
            inQuotes = 1
            cur += char
        elif char == '"' and inQuotes:
            inQuotes = 0
            cur += char
        else:
            cur += char
    args.append(cur)
    return args

Im Vergleich zu größeren Zeichenfolgen ist Ihre Funktion so langsam
Faran2007

3

Geschwindigkeitstest verschiedener Antworten:

import re
import shlex
import csv

line = 'this is "a test"'

%timeit [p for p in re.split("( |\\\".*?\\\"|'.*?')", line) if p.strip()]
100000 loops, best of 3: 5.17 µs per loop

%timeit re.findall(r'[^"\s]\S*|".+?"', line)
100000 loops, best of 3: 2.88 µs per loop

%timeit list(csv.reader([line], delimiter=" "))
The slowest run took 9.62 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 2.4 µs per loop

%timeit shlex.split(line)
10000 loops, best of 3: 50.2 µs per loop

1

Hmm, ich kann den "Antworten" -Button anscheinend nicht finden ... diese Antwort basiert auf dem Ansatz von Kate, teilt jedoch Zeichenfolgen korrekt mit Teilzeichenfolgen, die maskierte Anführungszeichen enthalten, und entfernt auch die Start- und Endanführungszeichen der Teilzeichenfolgen:

  [i.strip('"').strip("'") for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]

Dies funktioniert mit Strings wie 'This is " a \\\"test\\\"\\\'s substring"'(das verrückte Markup ist leider notwendig, um Python davon abzuhalten, die Escapezeichen zu entfernen).

Wenn die resultierenden Escapezeichen in den Zeichenfolgen in der zurückgegebenen Liste nicht erwünscht sind, können Sie diese leicht geänderte Version der Funktion verwenden:

[i.strip('"').strip("'").decode('string_escape') for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]

1

Um die Unicode-Probleme in einigen Python 2-Versionen zu umgehen, schlage ich vor:

from shlex import split as _split
split = lambda a: [b.decode('utf-8') for b in _split(a.encode('utf-8'))]

Für Python 2.7.5 sollte dies sein: split = lambda a: [b.decode('utf-8') for b in _split(a)]Andernfalls erhalten Sie:UnicodeDecodeError: 'ascii' codec can't decode byte ... in position ...: ordinal not in range(128)
Peter Varo

1

Als Option versuchen Sie tssplit:

In [1]: from tssplit import tssplit
In [2]: tssplit('this is "a test"', quote='"', delimiter='')
Out[2]: ['this', 'is', 'a test']

0

Ich schlage vor:

Testzeichenfolge:

s = 'abc "ad" \'fg\' "kk\'rdt\'" zzz"34"zzz "" \'\''

auch "" und '' erfassen:

import re
re.findall(r'"[^"]*"|\'[^\']*\'|[^"\'\s]+',s)

Ergebnis:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz', '""', "''"]

leere "" und '' zu ignorieren:

import re
re.findall(r'"[^"]+"|\'[^\']+\'|[^"\'\s]+',s)

Ergebnis:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz']

Könnte wie re.findall("(?:\".*?\"|'.*?'|[^\s'\"]+)", s)auch geschrieben werden .
hochl

-3

Wenn Sie sich nicht für Unterzeichenfolgen interessieren, dann für eine einfache

>>> 'a short sized string with spaces '.split()

Performance:

>>> s = " ('a short sized string with spaces '*100).split() "
>>> t = timeit.Timer(stmt=s)
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
171.39 usec/pass

Oder String-Modul

>>> from string import split as stringsplit; 
>>> stringsplit('a short sized string with spaces '*100)

Leistung: Das String-Modul scheint eine bessere Leistung zu erzielen als String-Methoden

>>> s = "stringsplit('a short sized string with spaces '*100)"
>>> t = timeit.Timer(s, "from string import split as stringsplit")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
154.88 usec/pass

Oder Sie können die RE-Engine verwenden

>>> from re import split as resplit
>>> regex = '\s+'
>>> medstring = 'a short sized string with spaces '*100
>>> resplit(regex, medstring)

Performance

>>> s = "resplit(regex, medstring)"
>>> t = timeit.Timer(s, "from re import split as resplit; regex='\s+'; medstring='a short sized string with spaces '*100")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
540.21 usec/pass

Bei sehr langen Zeichenfolgen sollten Sie nicht die gesamte Zeichenfolge in den Speicher laden und stattdessen entweder die Zeilen teilen oder eine iterative Schleife verwenden


11
Sie scheinen den ganzen Punkt der Frage übersehen zu haben. Es gibt Anführungszeichen in der Zeichenfolge, die nicht geteilt werden müssen.
rjmunro

-3

Versuche dies:

  def adamsplit(s):
    result = []
    inquotes = False
    for substring in s.split('"'):
      if not inquotes:
        result.extend(substring.split())
      else:
        result.append(substring)
      inquotes = not inquotes
    return result

Einige Testzeichenfolgen:

'This is "a test"' -> ['This', 'is', 'a test']
'"This is \'a test\'"' -> ["This is 'a test'"]

Bitte geben Sie den Repräsentanten einer Zeichenfolge an, von der Sie glauben, dass sie fehlschlagen wird.
pjz

Denken Sie ? adamsplit("This is 'a test'")['This', 'is', "'a", "test'"]
Matthew Schinckel

OP sagt nur "innerhalb von Anführungszeichen" und hat nur ein Beispiel mit doppelten Anführungszeichen.
pjz
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.