¿Cómo puedo eliminar las secuencias de escape ANSI de una cadena en Python?
Aquí hay un fragmento que incluye mi cadena.
'ls\r\n\x1b[00m\x1b[01;31mexamplefile.zip\x1b[00m\r\n\x1b[01;31m'
La cadena fue devuelta por un comando SSH que ejecuté. No puedo usar la cadena en su estado actual porque contiene secuencias de escape estandarizadas ANSI. ¿Cómo puedo eliminar mediante programación las secuencias de escape para que la única parte de la cadena que quede sea 'examplefile.zip'
.
Bórralos con una expresión regular:
import re
# 7-bit C1 ANSI sequences
ansi_escape = re.compile(r'''
\x1B # ESC
(?: # 7-bit C1 Fe (except CSI)
[@-Z\\-_]
| # or [ for CSI, followed by a control sequence
\[
[0-?]* # Parameter bytes
[ -/]* # Intermediate bytes
[@-~] # Final byte
)
''', re.VERBOSE)
result = ansi_escape.sub('', sometext)
o, sin la VERBOSE
bandera, en forma condensada:
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
result = ansi_escape.sub('', sometext)
Manifestación:
>>> import re
>>> ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
>>> sometext = 'ls\r\n\x1b[00m\x1b[01;31mexamplefile.zip\x1b[00m\r\n\x1b[01;31m'
>>> ansi_escape.sub('', sometext)
'ls\r\nexamplefile.zip\r\n'
La expresión regular anterior cubre todas las secuencias de escape ANSI C1 de 7 bits, pero no los abridores de secuencia de escape C1 de 8 bits. Estos últimos nunca se utilizan en el mundo UTF-8 actual, donde el mismo rango de bytes tiene un significado diferente.
Si también necesita cubrir los códigos de 8 bits (y, presumiblemente, está trabajando con bytes
valores), entonces la expresión regular se convierte en un patrón de bytes como este:
# 7-bit and 8-bit C1 ANSI sequences
ansi_escape_8bit = re.compile(br'''
(?: # either 7-bit C1, two bytes, ESC Fe (omitting CSI)
\x1B
[@-Z\\-_]
| # or a single 8-bit byte Fe (omitting CSI)
[\x80-\x9A\x9C-\x9F]
| # or CSI + control codes
(?: # 7-bit CSI, ESC [
\x1B\[
| # 8-bit CSI, 9B
\x9B
)
[0-?]* # Parameter bytes
[ -/]* # Intermediate bytes
[@-~] # Final byte
)
''', re.VERBOSE)
result = ansi_escape_8bit.sub(b'', somebytesvalue)
que se puede condensar hasta
# 7-bit and 8-bit C1 ANSI sequences
ansi_escape_8bit = re.compile(
br'(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])'
)
result = ansi_escape_8bit.sub(b'', somebytesvalue)
Para más información, ver:
- descripción general de los códigos de escape ANSI en Wikipedia
- Norma ECMA-48, 5.ª edición (especialmente las secciones 5.3 y 5.4)
El ejemplo que proporcionó contiene 4 códigos CSI (Introductor de secuencia de control), marcados por los bytes de apertura \x1B[
o ESC[
, y cada uno contiene un código SGR (Select Graphic Rendition), porque cada uno termina en m
. Los parámetros (separados por ;
punto y coma) entre ellos le indican a su terminal qué atributos de representación gráfica usar. Entonces, para cada \x1B[....m
secuencia, los 3 códigos que se utilizan son:
- 0 (o
00
en este ejemplo): restablecer , deshabilitar todos los atributos - 1 (o
01
en el ejemplo): negrita - 31: rojo (primer plano)
Sin embargo, ANSI es más que solo códigos CSI SGR. Solo con CSI también puedes controlar el cursor, borrar líneas o toda la pantalla, o desplazarte (siempre que el terminal lo admita, por supuesto). Y más allá de CSI, hay códigos para seleccionar fuentes alternativas ( SS2
y SS3
), enviar 'mensajes privados' (piense en contraseñas), comunicarse con el terminal ( DCS
), el sistema operativo ( OSC
) o la aplicación misma ( APC
, una forma para que las aplicaciones agregar códigos de control personalizados al flujo de comunicación) y códigos adicionales para ayudar a definir cadenas ( SOS
, Inicio de cadena, ST
Terminador de cadena) o para restablecer todo a un estado base ( RIS
). Las expresiones regulares anteriores cubren todos estos.
Tenga en cuenta que la expresión regular anterior solo elimina los códigos ANSI C1, sin embargo, y no ningún dato adicional que esos códigos puedan estar marcando (como las cadenas enviadas entre un abridor OSC y el código ST de terminación). Eliminarlos requeriría trabajo adicional fuera del alcance de esta respuesta.
La respuesta aceptada solo tiene en cuenta las secuencias de escape estandarizadas ANSI que están formateadas para alterar los colores de primer plano y el estilo del texto. Muchas secuencias no terminan en 'm'
, como por ejemplo: posicionamiento del cursor, borrado y desplazamiento de regiones. El siguiente patrón intenta cubrir todos los casos más allá de establecer el color de primer plano y el estilo del texto.
A continuación se muestra la expresión regular para secuencias de control estandarizadas ANSI:
/(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]/
Referencias adicionales:
- ECMA-48 Sección 5.4
- código de escape ANSI
Función
Basado en la respuesta de Martijn Pieters♦ con la expresión regular de Jeff .
def escape_ansi(line):
ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
return ansi_escape.sub('', line)
Prueba
def test_remove_ansi_escape_sequence(self):
line = '\t\u001b[0;35mBlabla\u001b[0m \u001b[0;36m172.18.0.2\u001b[0m'
escaped_line = escape_ansi(line)
self.assertEqual(escaped_line, '\tBlabla 172.18.0.2')
Pruebas
Si desea ejecutarlo usted mismo, utilícelo python3
(mejor compatibilidad con Unicode, blablabla). Así es como debería ser el archivo de prueba:
import unittest
import re
def escape_ansi(line):
…
class TestStringMethods(unittest.TestCase):
def test_remove_ansi_escape_sequence(self):
…
if __name__ == '__main__':
unittest.main()