如何从python中的字符串中删除ANSI转义序列


Answers:


142

使用正则表达式删除它们:

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)

或者,在没有VERBOSE标志的情况下,以压缩形式:

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

演示:

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

上面的正则表达式涵盖所有7位ANSI C1转义序列,但包括8位C1转义序列打开器。后者在当今的UTF-8世界中从未使用过,在UTF-8世界中,相同范围的字节具有不同的含义。

如果您确实也需要覆盖8位代码(然后大概使用bytes值),则正则表达式将变成如下所示的字节模式:

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

可以浓缩为

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

有关更多信息,请参见:

您提供的示例包含4个CSI(控制序列介绍者)代码(以\x1B[ESC[开头字节标记),并且每个示例均包含一个SGR(选择图形呈现)代码,因为它们均以结尾m。这些参数;之间的参数(以分号分隔)告诉您的终端要使用哪些图形再现属性。因此,对于每个\x1B[....m序列,使用的3个代码是:

  • 0(或00在此示例中):reset,禁用所有属性
  • 1(或01在示例中):粗体
  • 31:红色(前景)

但是,ANSI不仅限于CSI SGR代码。仅使用CSI,您还可以控制光标,清除行或整个显示或滚动(当然,前提是终端支持此功能)。除CSI外,还有一些代码可以选择其他字体(SS2SS3),发送“私人消息”(如密码),与终端(DCS),OS(OSC)或应用程序本身(APC,用于应用程序将自定义控制代码搭载到通信流上),以及用于帮助定义字符串(SOS,字符串的开始,ST字符串终止符)或将所有内容重置为基本状态的其他代码(RIS)。以上正则表达式涵盖了所有这些。

请注意,上面的正则表达式仅删除ANSI C1代码,而不会删除这些代码可能要标记的任何其他数据(例如OSC打开器和终止ST代码之间发送的字符串)。删除这些将需要在此答案范围之外的其他工作。


44

该问题的公认答案仅考虑颜色和字体效果。有很多不以“ m”结尾的序列,例如光标定位,擦除和滚动区域。

控制序列(又名ANSI转义序列)的完整正则表达式为

/(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]/

请参阅ECMA-48第5.4节ANSI转义码


1
它错过了OSC(开始和结束)。
托马斯·迪基

1
OSC在ECMA-48秒内。5.6-在这里提出这个问题有什么意义?
杰夫

3
OSC是“ ANSI转义序列”,经常使用,并且将以不同的模式开始。您的答案不完整
Thomas Dickey

这不适用于bluetoothctl,例如产生的颜色代码\x1b[0;94m。使得表达不区分大小写或更换1B1b在图案并没有区别。我正在使用Python和line re.compile(r'/(\x9b|\x1b\[)[0-?]*[ -\/]*[@-~]/', re.I)。然后我正在做pattern.sub("", my_string)任何事情都做不到。难道我做错了什么?
Hubro

1
我看到三个问题这样的回答:1)/.../不是Python语法,而是语法,你会在VI或Perl或awk的使用。2)\x9B开启程序(用于CSI代码)与UTF-8不兼容,因此现在很少使用,并且[首选ESC ; 3)您的模式仅涵盖CSI代码,而不是整个ANSI转义符范围(不仅包括OSC, Thomas Dickly提到了,但是还有SS2,SS3,DCS,ST,OSC,SOS,PM,APC和RIS)!
马丁·彼得斯

30

功能

基于的Martijn Pieters的♦的回答杰夫的正则表达式

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

测试

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

测验

如果您想自己运行它,请使用python3(更好的Unicode支持,等等)。测试文件应如下所示:

import unittest
import re

def escape_ansi(line):class TestStringMethods(unittest.TestCase):
    def test_remove_ansi_escape_sequence(self):if __name__ == '__main__':
    unittest.main()

为什么/在倒数第二个字符集中留下了逃脱者[ -\/]
安德鲁·盖尔纳

1
@AndrewGelnar @ÉdouardLopez[ -/]就足够了。
罗德里戈·马丁斯·德·奥利维拉

1
长期以来,我的正则表达式已扩展到涵盖所有ANSI C1代码(7位),并且今天我还添加了单独的8位变体。
的Martijn Pieters的

7

建议的正则表达式对我没有帮助,因此我创建了自己的正则表达式。以下是我根据此处找到的规范创建的python正则表达式

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)

我在以下代码段上测试了我的正则表达式(基本上是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

希望这会帮助其他人:)


该规范还不完整,该标准允许VT100不使用但其他终端使用的大量扩展,因此您的正则表达式过于冗长。
马丁·彼得斯

您的模式也有几个怪异的差异。ESC- O(SS3)将终端“切换”到备用字体模式,并且在该特定模式下解释下一个字节。在该模式下的可能值不限于mnl,或p通过z。我什至不去掉SS3之后的字节。SS2基本具有相同的功能(只是字体不同),但是您的正则表达式不会提取下一个字节。
马丁·彼得斯

最后但并非最不重要的一点是,您的正则表达式实际上无法删除问题示例中的完整ANSI代码,因为它遗留了m最后一个字节。
马丁·皮特斯

0

对于2020,使用python 3.5就像 string.encode().decode('ascii')

ascii_string = 'ls\r\n\x1b[00m\x1b[01;31mexamplefile.zip\x1b[00m\r\n\x1b[01;31m'
decoded_string = ascii_string.encode().decode('ascii')
print(decoded_string) 

>ls
>examplefile.zip
>

-1

如果它对将来的Stack Overflowers有帮助,我正在使用crayons库为我的Python输出提供更多的视觉效果,这是有利的,因为它可在Windows和Linux平台上使用。但是,我既在屏幕上显示内容又在日志文件上追加内容,转义序列影响了日志文件的易读性,因此想将其删除。但是,蜡笔插入的转义序列产生了一个错误:

expected string or bytes-like object

解决方案是将参数强制转换为字符串,因此只需对公认的答案进行微小的修改即可:

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

不过,这并不是真正的相同问题。有负载可能产生自定义对象那套字符串不同的库,在这里我们不认为需要转换成字符串正则表达式之前的作品对他们需要的答案为每个变种。
马丁·彼得斯

-3

如果要删除该\r\n位,可以通过此函数(由sarnold编写)传递字符串:

def stripEscape(string):
    """ Removes all escape sequences from the input string """
    delete = ""
    i=1
    while (i<0x20):
        delete += chr(i)
        i += 1
    t = string.translate(None, delete)
    return t

但是请小心,这会将转义序列前后的文本合并在一起。因此,使用Martijn的过滤字符串'ls\r\nexamplefile.zip\r\n',您将得到lsexamplefile.zip。请注意ls所需文件名的前面。

我将首先使用stripEscape函数删除转义序列,然后将输出传递给Martijn的正则表达式,这将避免串联不需要的位。


该问题并不要求删除空格,仅要求ANSI转义码。您sarnold的翻译string.translate()选项是不完全习惯或者(为什么使用whileforxrange()会做,例如''.join([chr(i) for i in range(0x20)])),而不是适用于Python 3中(在那里你可以只使用dict.fromkeys(range(0x20)))作为string.translate()地图)。
马丁·彼得斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.