it-swarm-eu.dev

Teilen Sie eine Zeichenfolge in Python nach Leerzeichen auf, wobei die in Anführungszeichen genannten Teilzeichenfolgen beibehalten werden

Ich habe einen String, der so 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 Ergebnis, das ich suche, ist:

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

PS. Ich weiß, dass Sie fragen werden, "was passiert, wenn in den Anführungszeichen Anführungszeichen stehen. In meiner Bewerbung wird das niemals passieren.

223
Adam Pierce

Sie möchten vom shlex - Modul getrennt werden.

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

Dies sollte genau das tun, was Sie wollen.

342
Jerub

Schauen Sie sich das Modul shlex an, insbesondere shlex.split.

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

Ich sehe Regex-Ansätze, die komplex und/oder falsch aussehen. Dies überrascht mich, da die Regex-Syntax "Whitespace oder Dings-umgeben von Anführungszeichen" leicht beschreiben kann und die meisten Regex-Engines (einschließlich Pythons) auf Regex aufgeteilt werden können. Wenn Sie also Regex verwenden, warum sagen Sie nicht 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 wahrscheinlich mehr Funktionen.

31
Kate

Abhängig von Ihrem Anwendungsfall möchten Sie möglicherweise auch das CSV-Modul auschecken:

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']
23
Ryan Ginstrom

Ich verwende shlex.split, um 70.000.000 Zeilen von Squid Log zu verarbeiten, es ist so langsam. Also wechselte ich zu re.

Bitte versuchen Sie dies, wenn Sie ein Leistungsproblem mit Shlex haben.

import re

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

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-Teilen mit\x00, spalte dann durch Leerzeichen auf und ersetze dann\x00 in jedem Teil wieder durch Leerzeichen.

Beide Versionen machen dasselbe, aber der Splitter ist etwas lesbarer als der 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)
8
gooli

Um Anführungszeichen zu erhalten, verwenden Sie diese Funktion:

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
3
THE_MAD_KING

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
2
har777

Es scheint, dass re aus Performancegründen schneller ist. Hier ist meine Lösung mit einem am wenigsten gierigen Operator, der die äußeren Zitate beibehält:

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

Ergebnis:

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

Konstrukte wie aaa"bla blub"bbb werden zusammen gelassen, da diese Token nicht durch Leerzeichen getrennt sind. Wenn die Zeichenfolge Zeichen enthält, die mit Escapezeichen versehen sind, können Sie folgendermaßen ü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 ebenfalls mit dem leeren String "" anhand des \S-Teils des Musters übereinstimmt.

2
hochl

Um die Unicode-Probleme in einigen Python 2-Versionen zu umgehen, empfehle ich Folgendes:

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

Das Hauptproblem bei der akzeptierten shlex-Methode ist, dass Escape-Zeichen außerhalb der in Anführungszeichen genannten Unterzeichenfolgen nicht ignoriert werden und in einigen Eckfällen etwas unerwartete Ergebnisse erzielt werden.

Ich habe den folgenden Anwendungsfall, bei dem ich eine Split-Funktion benötige, die Eingabezeichenfolgen so aufteilt, dass entweder einteilige oder doppelte Anführungszeichen beibehalten werden und die Möglichkeit besteht, Anführungszeichen innerhalb einer solchen Teilzeichenfolge zu umgehen. Anführungszeichen innerhalb einer nicht zitierten Zeichenfolge sollten nicht anders als andere Zeichen behandelt werden. 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 sich die erwartete Ausgabe für alle Eingabezeichenfolgen ergibt:

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 (shlex und csv für jetzt) ​​und die Implementierung des benutzerdefinierten Split:

#!/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 __== '__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 schließ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 schließendes Zitat 
 [FAIL] abc'def '-> [' abcdef '] 
 [FAIL] abc'def' ghi -> ['abcdef', 'ghi'] 
 [FAIL] abc'def'ghi -> ['abcdefghi'] 
 [FAIL] abc "def -> Ausnahme: Kein Schlusszitat 
 [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 shlex und kann durch Vorkompilierung des regulären Ausdrucks weiter verbessert werden. In diesem Fall übertrifft er die csv-Methode.

1

Die oben diskutierten Unicode-Probleme mit shlex (oberste Antwort) scheinen (indirekt) in 2.7.2+ gemäß http://bugs.python.org/issue6988#msg146200 gelöst zu werden.

(separate Antwort, da ich keinen Kommentar abgeben kann)

1
Tyris

Hmm, scheint den "Antworten" -Button nicht zu finden ... diese Antwort basiert ohnehin auf der Annäherung von Kate, teilt jedoch die Zeichenfolgen korrekt mit Teilzeichenfolgen mit Escape-Anführungszeichen auf und entfernt auch die Start- und End-Anführungszeichen der Teilzeichenfolgen:

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

Dies funktioniert bei Zeichenketten wie 'This is " a \\\"test\\\"\\\'s substring"' (das verrückte Markup ist leider notwendig, um zu verhindern, dass Python die Escape-Zeichen entfernt).

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

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

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', '""', "''"]

leeres "" und '' ignorieren:

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

ergebnis:

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