Основна проблема з прийнятим shlex
підходу полягає в тому, що він не ігнорує втечі символів поза цитованими підрядками та дає дещо несподівані результати в деяких кутових випадках.
У мене є наступний випадок використання, коли мені потрібна функція розбиття, яка розбиває рядки введення таким чином, щоб збереглися або одноцитати, або подвійні котирування, з можливістю вийти з лапок у такій підрядку. Цитати в рядку, що не котирується, не повинні трактуватися інакше, ніж будь-який інший символ. Деякі приклади тестових випадків із очікуваним результатом:
вхідний рядок | очікуваний вихід
=================================================
'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']
'"" тест "| ['', 'тест']
"'" тест "| ['', 'тест']
"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 $' "]
Я закінчив наступну функцію розділити рядок таким чином, щоб очікувані результати виводилися для всіх вхідних рядків:
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)]
У наступному тестовому додатку перевіряються результати інших підходів ( shlex
і csv
наразі) та спеціальна реалізація розбиття:
#!/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)')
Вихід:
шлекс
[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 -> виняток: Без закриття пропозиції
[OK] 'abc \ s def' ghi -> ['abc \\ s def', 'ghi']
[OK] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[OK] "" тест -> ['', 'test']
[OK] '' test -> ['', 'test']
[FAIL] abc'def -> виняток: немає завершальної пропозиції
[FAIL] abc'def '-> [' abcdef ']
[FAIL] abc'def 'ghi -> [' abcdef ',' ghi ']
[FAIL] abc'def'ghi -> ['abcdefghi']
[FAIL] abc "def -> виняток: Без закриття пропозиції
[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']
[FAIL] '' test -> ["''", 'test']
[ОК] abc'def -> ["abc'def"]
[OK] abc'def '-> ["abc'def'"]
[Добре] abc'def 'ghi -> ["abc'def'", "ghi"]
[Добре] 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 "]
[Гаразд] r'AA 'r'. * _ Xyz $ '-> ["r'AA" "," r'. * _ Xyz $ '"]
повторно
[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']
[OK] '' test -> ['', 'test']
[ОК] abc'def -> ["abc'def"]
[OK] abc'def '-> ["abc'def'"]
[Добре] abc'def 'ghi -> ["abc'def'", "ghi"]
[Добре] 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 "]
[Гаразд] r'AA 'r'. * _ Xyz $ '-> ["r'AA" "," r'. * _ Xyz $ '"]
shlex: 0,281ms за ітерацію
csv: 0,030ms за ітерацію
re: 0,049 мс за ітерацію
Таким чином, продуктивність набагато краща, ніж shlex
її можна покращити, попередньо склавши регулярний вираз, і в цьому випадку це перевершить csv
підхід.