Das Hauptproblem bei dem akzeptierten shlex
Ansatz 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 ( shlex
und csv
vorerst) 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 shlex
und kann durch Vorkompilieren des regulären Ausdrucks weiter verbessert werden. In diesem Fall wird der csv
Ansatz übertroffen .