103 votes

Comment puis-je supprimer les séquences d'échappement ANSI d'une chaîne de caractères en python ?

Voici un extrait qui inclut ma chaîne de caractères.
'ls\r\n\x1b[00m\x1b[01;31mexamplefile.zip\x1b[00m\r\n\x1b[01;31m'

La chaîne a été renvoyée par une commande SSH que j'ai exécutée. Je ne peux pas utiliser la chaîne dans son état actuel car elle contient des séquences d'échappement normalisées ANSI. Comment puis-je supprimer par programme les séquences d'échappement de sorte que la seule partie de la chaîne qui reste soit 'examplefile.zip' .

204voto

Martijn Pieters Points 271458

Supprimez-les avec une expression régulière :

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)

ou, sans le VERBOSE drapeau, sous forme condensée :

ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
result = ansi_escape.sub('', sometext)

Démonstration :

>>> 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'

L'expression régulière ci-dessus couvre toutes les séquences d'échappement ANSI C1 de 7 bits, mais pas les ouvreurs de séquence d'échappement C1 8 bits. Ces derniers ne sont jamais utilisés dans le monde UTF-8 actuel, où la même plage d'octets a une signification différente.

Si vous avez besoin de couvrir également les codes 8 bits (et que vous travaillez alors, vraisemblablement, avec bytes ), l'expression régulière devient alors un motif d'octets comme celui-ci :

# 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)

qui peut être condensé en

# 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)

Pour plus d'informations, voir :

L'exemple que vous avez donné contient 4 codes CSI (Control Sequence Introducer), comme l'indique le symbole \x1B[ o ESC [ et chacun d'entre eux contient un code SGR (Select Graphic Rendition), car ils se terminent tous par l'expression m . Les paramètres (séparés par ; Les points-virgules) entre ceux-ci indiquent à votre terminal les attributs de rendu graphique à utiliser. Ainsi, pour chaque \x1B[....m les 3 codes utilisés sont les suivants :

  • 0 (ou 00 dans cet exemple) : réinitialiser Désactiver tous les attributs
  • 1 (ou 01 dans l'exemple) : en gras
  • 31 : rouge (premier plan)

Cependant, l'ANSI ne se limite pas aux codes CSI SGR. Avec CSI uniquement, vous pouvez également contrôler le curseur, effacer des lignes ou l'ensemble de l'affichage, ou encore faire défiler l'écran (à condition que le terminal le supporte, bien sûr). Et au-delà de CSI, il existe des codes pour sélectionner des polices alternatives ( SS2 y SS3 ), pour envoyer des "messages privés" (pensez aux mots de passe), pour communiquer avec le terminal ( DCS ), l'OS ( OSC ), ou l'application elle-même ( APC un moyen pour les applications d'ajouter des codes de contrôle personnalisés au flux de communication), et d'autres codes pour aider à définir les chaînes de caractères ( SOS , Début de la chaîne, ST String Terminator) ou pour tout réinitialiser à un état de base ( RIS ). Les regex ci-dessus couvrent tous ces éléments.

Notez que la regex ci-dessus ne supprime que les codes ANSI C1, et non les données supplémentaires que ces codes peuvent marquer (comme les chaînes envoyées entre un ouvreur OSC et le code ST de terminaison). Leur suppression nécessiterait un travail supplémentaire qui sort du cadre de cette réponse.

52voto

Jeff Points 351

La réponse acceptée ne prend en compte que les séquences d'échappement normalisées ANSI qui sont formatées pour modifier les couleurs de premier plan et le style du texte. De nombreuses séquences ne se terminent pas par 'm' tels que le positionnement du curseur, l'effacement et les zones de défilement. Le modèle ci-dessous tente de couvrir tous les cas au-delà du réglage de la couleur de premier plan et du style de texte.

Voici l'expression régulière pour les séquences de contrôle normalisées ANSI :
/(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]/

Références supplémentaires :

40voto

Édouard Lopez Points 2762

Fonction

Sur la base de La réponse de Martijn Pieters avec Le regexp de Jeff .

def escape_ansi(line):
    ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
    return ansi_escape.sub('', line)

Test

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')

Essais

Si vous voulez l'exécuter par vous-même, utilisez python3 (meilleur support de l'unicode, blablabla). Voici comment le fichier de test devrait être :

import unittest
import re

def escape_ansi(line):
    …

class TestStringMethods(unittest.TestCase):
    def test_remove_ansi_escape_sequence(self):
    …

if __name__ == '__main__':
    unittest.main()

8voto

kfir Points 303

La regex proposée n'a pas fonctionné pour moi, j'en ai donc créé une moi-même. Ce qui suit est une regex python que j'ai créée sur la base de la spécification trouvée aquí

ansi_regex = r'\x1b(' \
             r'(\[\??\d+[hl])|' \
             r'([=<>a-kzNM78])|' \
             r'([\(\)][a-b0-2])|' \
             r'(\[\d{0,2}[ma-dgkjqi])|' \
             r'(\[\d+;\d+[hfy]?)|' \
             r'(\[;?[hf])|' \
             r'(#[3-68])|' \
             r'([01356]n)|' \
             r'(O[mlnp-z]?)|' \
             r'(/Z)|' \
             r'(\d+)|' \
             r'(\[\?\d;\d0c)|' \
             r'(\d;\dR))'
ansi_escape = re.compile(ansi_regex, flags=re.IGNORECASE)

J'ai testé ma regex sur l'extrait suivant (en fait un copier-coller de la page ascii-table.com)

\x1b[20h    Set
\x1b[?1h    Set
\x1b[?3h    Set
\x1b[?4h    Set
\x1b[?5h    Set
\x1b[?6h    Set
\x1b[?7h    Set
\x1b[?8h    Set
\x1b[?9h    Set
\x1b[20l    Set
\x1b[?1l    Set
\x1b[?2l    Set
\x1b[?3l    Set
\x1b[?4l    Set
\x1b[?5l    Set
\x1b[?6l    Set
\x1b[?7l    Reset
\x1b[?8l    Reset
\x1b[?9l    Reset
\x1b=   Set
\x1b>   Set
\x1b(A  Set
\x1b)A  Set
\x1b(B  Set
\x1b)B  Set
\x1b(0  Set
\x1b)0  Set
\x1b(1  Set
\x1b)1  Set
\x1b(2  Set
\x1b)2  Set
\x1bN   Set
\x1bO   Set
\x1b[m  Turn
\x1b[0m Turn
\x1b[1m Turn
\x1b[2m Turn
\x1b[4m Turn
\x1b[5m Turn
\x1b[7m Turn
\x1b[8m Turn
\x1b[1;2    Set
\x1b[1A Move
\x1b[2B Move
\x1b[3C Move
\x1b[4D Move
\x1b[H  Move
\x1b[;H Move
\x1b[4;3H   Move
\x1b[f  Move
\x1b[;f Move
\x1b[1;2    Move
\x1bD   Move/scroll
\x1bM   Move/scroll
\x1bE   Move
\x1b7   Save
\x1b8   Restore
\x1bH   Set
\x1b[g  Clear
\x1b[0g Clear
\x1b[3g Clear
\x1b#3  Double-height
\x1b#4  Double-height
\x1b#5  Single
\x1b#6  Double
\x1b[K  Clear
\x1b[0K Clear
\x1b[1K Clear
\x1b[2K Clear
\x1b[J  Clear
\x1b[0J Clear
\x1b[1J Clear
\x1b[2J Clear
\x1b5n  Device
\x1b0n  Response:
\x1b3n  Response:
\x1b6n  Get
\x1b[c  Identify
\x1b[0c Identify
\x1b[?1;20c Response:
\x1bc   Reset
\x1b#8  Screen
\x1b[2;1y   Confidence
\x1b[2;2y   Confidence
\x1b[2;9y   Repeat
\x1b[2;10y  Repeat
\x1b[0q Turn
\x1b[1q Turn
\x1b[2q Turn
\x1b[3q Turn
\x1b[4q Turn
\x1b<   Enter/exit
\x1b=   Enter
\x1b>   Exit
\x1bF   Use
\x1bG   Use
\x1bA   Move
\x1bB   Move
\x1bC   Move
\x1bD   Move
\x1bH   Move
\x1b12  Move
\x1bI  
\x1bK  
\x1bJ  
\x1bZ  
\x1b/Z 
\x1bOP 
\x1bOQ 
\x1bOR 
\x1bOS 
\x1bA  
\x1bB  
\x1bC  
\x1bD  
\x1bOp 
\x1bOq 
\x1bOr 
\x1bOs 
\x1bOt 
\x1bOu 
\x1bOv 
\x1bOw 
\x1bOx 
\x1bOy 
\x1bOm 
\x1bOl 
\x1bOn 
\x1bOM 
\x1b[i 
\x1b[1i
\x1b[4i
\x1b[5i

J'espère que cela aidera d'autres personnes :)

0voto

Rory Points 26

Si cela peut aider les futurs Stack Overflowers, j'utilisais la bibliothèque des crayons pour donner à ma sortie Python un peu plus d'impact visuel, ce qui est avantageux car il fonctionne à la fois sur les plateformes Windows et Linux. Cependant, j'affichais à l'écran et j'annexais aux fichiers journaux, et les séquences d'échappement avaient un impact sur la lisibilité des fichiers journaux, je voulais donc les supprimer. Cependant, les séquences d'échappement insérées par les crayons ont produit une erreur :

expected string or bytes-like object

La solution consistait à transformer le paramètre en une chaîne de caractères, de sorte que seule une modification minime de la réponse communément acceptée était nécessaire :

def escape_ansi(line):
    ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
    return ansi_escape.sub('', str(line))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X