El principal problema con el shlex
enfoque aceptado es que no ignora los caracteres de escape fuera de las subcadenas citadas, y da resultados ligeramente inesperados en algunos casos de esquina.
Tengo el siguiente caso de uso, donde necesito una función dividida que divida las cadenas de entrada de modo que se mantengan las subcadenas entre comillas simples o dobles, con la capacidad de escapar de las comillas dentro de dicha subcadena. Las comillas dentro de una cadena sin comillas no deben tratarse de manera diferente a cualquier otro carácter. Algunos ejemplos de casos de prueba con la salida esperada:
cadena de entrada | Rendimiento esperado
===============================================
'abc def' | ['a B C D e F']
"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']
'"" prueba' | ['', 'prueba']
"'' prueba" | ['', 'prueba']
"abc'def" | ["a B C D e F"]
"abc'def '" | ["a B C D e F'"]
"abc'def 'ghi" | ["abc'def '",' ghi ']
"abc'def'ghi" | ["abc'def'ghi"]
'abc "def' | ['abc" def']
'abc "def"' | ['a B C D e F"']
'abc "def" ghi' | ['abc "def"', 'ghi']
'abc "def" ghi' | ['abc "def" ghi']
"r'AA 'r'. * _ xyz $ '" | ["r'AA '", "r'. * _ xyz $ '"]
Terminé con la siguiente función para dividir una cadena de modo que los resultados de salida esperados para todas las cadenas de entrada:
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)]
La siguiente aplicación de prueba verifica los resultados de otros enfoques ( shlex
y csv
por ahora) y la implementación de división personalizada:
#!/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)')
Salida:
shlex
[OK] abc def -> ['abc', 'def']
[FALLO] 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']
[FALLO] 'abc \' def 'ghi -> excepción: sin presupuesto de cierre
[OK] 'abc \ s def' ghi -> ['abc \\ s def', 'ghi']
[OK] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[OK] "" prueba -> ['', 'prueba']
[OK] '' prueba -> ['', 'prueba']
[FAIL] abc'def -> excepción: sin presupuesto de cierre
[FALLO] abc'def '-> [' abcdef ']
[FALLO] abc'def 'ghi -> [' abcdef ',' ghi ']
[FALLO] abc'def'ghi -> ['abcdefghi']
[FAIL] abc "def -> excepción: sin presupuesto de cierre
[FALLO] abc "def" -> ['abcdef']
[FALLO] abc "def" ghi -> ['abcdef', 'ghi']
[FALLO] abc "def" ghi -> ['abcdefghi']
[FALLO] 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']
[FALLO] 'abc def' ghi -> ["'abc", "def'", 'ghi']
[FALLO] "abc \" def "ghi -> ['abc \\', 'def"', 'ghi']
[FALLO] 'abc \' def 'ghi -> ["' abc", "\\ '", "def'", 'ghi']
[FALLO] 'abc \ s def' ghi -> ["'abc",' \\ s ', "def'", 'ghi']
[OK] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[OK] "" prueba -> ['', 'prueba']
[FALLO] '' prueba -> ["''", 'prueba']
[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] "" prueba -> ['', 'prueba']
[OK] '' prueba -> ['', 'prueba']
[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.281ms por iteración
csv: 0.030ms por iteración
re: 0.049ms por iteración
Por lo tanto, el rendimiento es mucho mejor shlex
y se puede mejorar aún más mediante la precompilación de la expresión regular, en cuyo caso superará el csv
enfoque.