shlex — Análisis de sintaxis de estilo shell¶
Propósito: | Análisis léxico de sintaxis de estilo shell. |
---|
El módulo shlex
implementa una clase para analizar sintaxis simples tipo
shell. Se puede usar para escribir un lenguaje específico de dominio o para
analizar cadenas entre comillas (una tarea que es más compleja de lo que parece
en la superficie).
Analizar cadenas entrecomilladas¶
Un problema común cuando se trabaja con texto de entrada es identificar una secuencia de palabras entrecomilladas como una sola entidad. Dividir el texto entre comillas no siempre funciona como se esperaba, especialmente si hay niveles anidados de comillas. Toma el siguiente texto como ejemplo.
This string has embedded "double quotes" and
'single quotes' in it, and even "a 'nested example'".
Un enfoque ingenuo sería construir una expresión regular para encontrar las
partes del texto fuera de las comillas para separarlas del texto dentro de las
comillas, o viceversa. Eso sería innecesariamente complejo y propenso a errores
resultantes de casos extremos como apóstrofes o incluso errores tipográficos.
Una mejor solución es usar un analizador verdadero, como el que proporciona el
módulo shlex
. Aquí hay un ejemplo simple que imprime los tokens
identificados en el archivo de entrada usando la clase shlex
.
import shlex
import sys
if len(sys.argv) != 2:
print('Please specify one filename on the command line.')
sys.exit(1)
filename = sys.argv[1]
with open(filename, 'r') as f:
body = f.read()
print('ORIGINAL: {!r}'.format(body))
print()
print('TOKENS:')
lexer = shlex.shlex(body)
for token in lexer:
print('{!r}'.format(token))
Cuando se ejecuta en datos con comillas embebidas, el analizador produce la lista de tokens esperados.
$ python3 shlex_example.py quotes.txt
ORIGINAL: 'This string has embedded "double quotes" and\n\'singl
e quotes\' in it, and even "a \'nested example\'".\n'
TOKENS:
'This'
'string'
'has'
'embedded'
'"double quotes"'
'and'
"'single quotes'"
'in'
'it'
','
'and'
'even'
'"a \'nested example\'"'
'.'
Las comillas aisladas, como los apóstrofes, también se manejan. Considera este archivo de entrada.
This string has an embedded apostrophe, doesn't it?
El token con el apóstrofe embebido no es un problema.
$ python3 shlex_example.py apostrophe.txt
ORIGINAL: "This string has an embedded apostrophe, doesn't it?"
TOKENS:
'This'
'string'
'has'
'an'
'embedded'
'apostrophe'
','
"doesn't"
'it'
'?'
Hacer cadenas seguras para shells¶
La función quote()
realiza la operación inversa, escapa de las comillas
existentes y agrega comillas faltantes para que las cadenas sean seguras de usar
en los comandos de shell.
import shlex
examples = [
"Embedded'SingleQuote",
'Embedded"DoubleQuote',
'Embedded Space',
'~SpecialCharacter',
r'Back\slash',
]
for s in examples:
print('ORIGINAL : {}'.format(s))
print('QUOTED : {}'.format(shlex.quote(s)))
print()
Por lo general, es más seguro usar una lista de argumentos cuando se usa
subprocess.Popen
, pero en situaciones donde eso no es posible quote()
proporciona cierta protección al garantizar que los caracteres especiales y los
espacios en blanco se entrecomillan correctamente .
$ python3 shlex_quote.py
ORIGINAL : Embedded'SingleQuote
QUOTED : 'Embedded'"'"'SingleQuote'
ORIGINAL : Embedded"DoubleQuote
QUOTED : 'Embedded"DoubleQuote'
ORIGINAL : Embedded Space
QUOTED : 'Embedded Space'
ORIGINAL : ~SpecialCharacter
QUOTED : '~SpecialCharacter'
ORIGINAL : Back\slash
QUOTED : 'Back\slash'
Comentarios embebidos¶
Dado que el analizador está destinado a ser utilizado con lenguajes de comando,
necesita manejar comentarios. Por defecto, cualquier texto que sigue a un #
se considera parte de un comentario y se ignora. Debido a la naturaleza del
analizador, solo se admiten prefijos de comentarios de un solo carácter. El
conjunto de caracteres de comentario utilizado se puede configurar a través de
la propiedad commenters
.
$ python3 shlex_example.py comments.txt
ORIGINAL: 'This line is recognized.\n# But this line is ignored.
\nAnd this line is processed.'
TOKENS:
'This'
'line'
'is'
'recognized'
'.'
'And'
'this'
'line'
'is'
'processed'
'.'
División de cadenas en tokens¶
Para dividir una cadena existente en tokens de componentes, la función de
conveniencia split()
es una envoltura simple alrededor del analizador.
import shlex
text = """This text has "quoted parts" inside it."""
print('ORIGINAL: {!r}'.format(text))
print()
print('TOKENS:')
print(shlex.split(text))
El resultado es una lista.
$ python3 shlex_split.py
ORIGINAL: 'This text has "quoted parts" inside it.'
TOKENS:
['This', 'text', 'has', 'quoted parts', 'inside', 'it.']
Incluir otras fuentes de tokens¶
La clase shlex
incluye varias propiedades de configuración que controlan su
comportamiento. La propiedad source
habilita una característica para la
reutilización de código (o configuración) al permitir que una secuencia de
tokens incluya otra. Esto es similar al operador source
de shell Bourne, de
ahí el nombre.
import shlex
text = "This text says to source quotes.txt before continuing."
print('ORIGINAL: {!r}'.format(text))
print()
lexer = shlex.shlex(text)
lexer.wordchars += '.'
lexer.source = 'source'
print('TOKENS:')
for token in lexer:
print('{!r}'.format(token))
La cadena «source quotes.txt
» en el texto original recibe un manejo
especial. Dado que la propiedad source
del lexer se establece en
"source"
, cuando se encuentra la palabra clave, el nombre de archivo que
aparece en la línea siguiente se incluye automáticamente. Para que el nombre de
archivo aparezca como un token único, el carácter .
debe agregarse a la
lista de caracteres que se incluyen en las palabras (de lo contrario,
«quotes.txt
» se convierte en tres tokens «quotes
», «.
»,»txt
»).
Así se ve la salida.
$ python3 shlex_source.py
ORIGINAL: 'This text says to source quotes.txt before
continuing.'
TOKENS:
'This'
'text'
'says'
'to'
'This'
'string'
'has'
'embedded'
'"double quotes"'
'and'
"'single quotes'"
'in'
'it'
','
'and'
'even'
'"a \'nested example\'"'
'.'
'before'
'continuing.'
La función fuente utiliza un método llamado sourcehook()
para cargar la
fuente de entrada adicional, por lo que una subclase de shlex
puede
proporcionar una implementación alternativa que cargue datos desde ubicaciones
que no sean archivos.
Control del analizador¶
Un ejemplo anterior demostró el cambio del valor wordchars
para
controlar qué caracteres se incluyen en las palabras. También es posible
establecer el carácter quotes
para usar comillas adicionales o
alternativas. Cada comilla debe ser un solo carácter, por lo que no es posible
tener comillas abiertas y cerradas diferentes (no paréntesis, por ejemplo).
import shlex
text = """|Col 1||Col 2||Col 3|"""
print('ORIGINAL: {!r}'.format(text))
print()
lexer = shlex.shlex(text)
lexer.quotes = '|'
print('TOKENS:')
for token in lexer:
print('{!r}'.format(token))
En este ejemplo, cada celda de la tabla está envuelta en barras verticales.
$ python3 shlex_table.py
ORIGINAL: '|Col 1||Col 2||Col 3|'
TOKENS:
'|Col 1|'
'|Col 2|'
'|Col 3|'
También es posible controlar los espacios en blanco utilizados para dividir palabras.
import shlex
import sys
if len(sys.argv) != 2:
print('Please specify one filename on the command line.')
sys.exit(1)
filename = sys.argv[1]
with open(filename, 'r') as f:
body = f.read()
print('ORIGINAL: {!r}'.format(body))
print()
print('TOKENS:')
lexer = shlex.shlex(body)
lexer.whitespace += '.,'
for token in lexer:
print('{!r}'.format(token))
Si el ejemplo en shlex_example.py
se modifica para incluir punto y coma, los
resultados cambian.
$ python3 shlex_whitespace.py quotes.txt
ORIGINAL: 'This string has embedded "double quotes" and\n\'singl
e quotes\' in it, and even "a \'nested example\'".\n'
TOKENS:
'This'
'string'
'has'
'embedded'
'"double quotes"'
'and'
"'single quotes'"
'in'
'it'
'and'
'even'
'"a \'nested example\'"'
Manejo de errores¶
Cuando el analizador encuentra el final de su entrada antes de que se cierren
todas las cadenas entrecomilladas, genera ValueError
. Cuando eso sucede, es
útil examinar algunas de las propiedades mantenidas por el analizador mientras
procesa la entrada. Por ejemplo, infile
se refiere al nombre del
archivo que se está procesando (que podría ser diferente del archivo original,
si un archivo obtiene otro). El lineno
informa la línea cuando se
descubre el error. El lineno
es típicamente el final del archivo, que
puede estar muy lejos de la primera comilla. El atributo token
contiene
el búfer de texto que no está incluido en un token válido. El método
error_leader()
produce un prefijo de mensaje en un estilo similar a los
compiladores de Unix, que permite a editores como emacs
analizar el error y
llevar al usuario directamente a la línea no válida.
import shlex
text = """This line is ok.
This line has an "unfinished quote.
This line is ok, too.
"""
print('ORIGINAL: {!r}'.format(text))
print()
lexer = shlex.shlex(text)
print('TOKENS:')
try:
for token in lexer:
print('{!r}'.format(token))
except ValueError as err:
first_line_of_error = lexer.token.splitlines()[0]
print('ERROR: {} {}'.format(lexer.error_leader(), err))
print('following {!r}'.format(first_line_of_error))
El ejemplo produce esta salida.
$ python3 shlex_errors.py
ORIGINAL: 'This line is ok.\nThis line has an "unfinished quote.
\nThis line is ok, too.\n'
TOKENS:
'This'
'line'
'is'
'ok'
'.'
'This'
'line'
'has'
'an'
ERROR: "None", line 4: No closing quotation
following '"unfinished quote.'
Análisis POSIX versus no POSIX¶
El comportamiento predeterminado para el analizador es usar un estilo compatible
con versiones anteriores que no sea compatible con POSIX. Para el
comportamiento POSIX, establece el argumento posix
cuando construyes el
analizador.
import shlex
examples = [
'Do"Not"Separate',
'"Do"Separate',
'Escaped \e Character not in quotes',
'Escaped "\e" Character in double quotes',
"Escaped '\e' Character in single quotes",
r"Escaped '\'' \"\'\" single quote",
r'Escaped "\"" \'\"\' double quote',
"\"'Strip extra layer of quotes'\"",
]
for s in examples:
print('ORIGINAL : {!r}'.format(s))
print('non-POSIX: ', end='')
non_posix_lexer = shlex.shlex(s, posix=False)
try:
print('{!r}'.format(list(non_posix_lexer)))
except ValueError as err:
print('error({})'.format(err))
print('POSIX : ', end='')
posix_lexer = shlex.shlex(s, posix=True)
try:
print('{!r}'.format(list(posix_lexer)))
except ValueError as err:
print('error({})'.format(err))
print()
Aquí hay algunos ejemplos de las diferencias en el comportamiento de análisis.
$ python3 shlex_posix.py
ORIGINAL : 'Do"Not"Separate'
non-POSIX: ['Do"Not"Separate']
POSIX : ['DoNotSeparate']
ORIGINAL : '"Do"Separate'
non-POSIX: ['"Do"', 'Separate']
POSIX : ['DoSeparate']
ORIGINAL : 'Escaped \\e Character not in quotes'
non-POSIX: ['Escaped', '\\', 'e', 'Character', 'not', 'in',
'quotes']
POSIX : ['Escaped', 'e', 'Character', 'not', 'in', 'quotes']
ORIGINAL : 'Escaped "\\e" Character in double quotes'
non-POSIX: ['Escaped', '"\\e"', 'Character', 'in', 'double',
'quotes']
POSIX : ['Escaped', '\\e', 'Character', 'in', 'double',
'quotes']
ORIGINAL : "Escaped '\\e' Character in single quotes"
non-POSIX: ['Escaped', "'\\e'", 'Character', 'in', 'single',
'quotes']
POSIX : ['Escaped', '\\e', 'Character', 'in', 'single',
'quotes']
ORIGINAL : 'Escaped \'\\\'\' \\"\\\'\\" single quote'
non-POSIX: error(No closing quotation)
POSIX : ['Escaped', '\\ \\"\\"', 'single', 'quote']
ORIGINAL : 'Escaped "\\"" \\\'\\"\\\' double quote'
non-POSIX: error(No closing quotation)
POSIX : ['Escaped', '"', '\'"\'', 'double', 'quote']
ORIGINAL : '"\'Strip extra layer of quotes\'"'
non-POSIX: ['"\'Strip extra layer of quotes\'"']
POSIX : ["'Strip extra layer of quotes'"]
Ver también
- Documentación de la biblioteca estándar para shlex
cmd
– Herramientas para construir intérpretes de comandos interactivos.argparse
– Análisis de opciones de línea de comando.subprocess
– Ejecuta comandos después de analizar la línea de comandos.