【问题标题】:How can I remove the ANSI escape sequences from a string in python如何从 python 中的字符串中删除 ANSI 转义序列
【发布时间】:2013-01-19 13:31:23
【问题描述】:

这是我的字符串:

'ls\r\n\x1b[00m\x1b[01;31mexamplefile.zip\x1b[00m\r\n\x1b[01;31m'

我正在使用代码从 SSH 命令检索输出,我希望我的字符串只包含“examplefile.zip”

我可以用什么来删除多余的转义序列?

【问题讨论】:

标签: python string escaping ansi-escape


【解决方案1】:

用正则表达式删除它们:

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 世界中从未使用过,相同的字节范围具有不同的含义。

如果您也确实需要覆盖 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):重置,禁用所有属性
  • 1(或示例中的01):粗体
  • 31:红色(前景)

但是,ANSI 不仅仅是 CSI SGR 代码。单独使用 CSI,您还可以控制光标、清除线条或整个显示或滚动(当然前提是终端支持)。除了 CSI,还有一些代码可以选择替代字体(SS2SS3),发送“私人消息”(想想密码),与终端通信(DCS),操作系统(OSC) ,或应用程序本身(APC,一种应用程序将自定义控制代码捎带到通信流的方式),以及帮助定义字符串的其他代码(SOS,字符串开始,ST 字符串终止符)或将所有内容重置为基本状态 (RIS)。上面的正则表达式涵盖了所有这些。

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

【讨论】:

    【解决方案2】:

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

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

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

    参考ECMA-48 Section 5.4ANSI escape code

    【讨论】:

    • 它错过了 OSC(包括开头和结尾)。
    • OSC 在 ECMA-48 秒内。 5.6 - 在这里提出这个有什么意义?
    • OSC 是一个“ANSI 转义序列”,经常使用,并且会以不同的模式开始。您的答案不完整
    • 这不适用于bluetoothctl 生成的颜色代码,例如:\x1b[0;94m。使表达式不区分大小写或在模式中将1B 替换为1b 没有任何区别。我正在使用 Python 和 re.compile(r'/(\x9b|\x1b\[)[0-?]*[ -\/]*[@-~]/', re.I) 行。然后我在做pattern.sub("", my_string),它什么也没做。我做错了吗?
    • 我看到这个答案存在三个问题:1) /.../ 不是 Python 语法,而是您在 VI、Perl 或 awk 中使用的语法。 2)\x9B 开启器(用于 CSI 代码)与 UTF-8 不兼容,因此现在很少使用,首选 ESC [ 和 3)您的模式仅涵盖 CSI 代码,而不是整个 ANSI 转义范围(其中不仅包括 Thomas Dickly 提到的 OSC,还包括 SS2、SS3、DCS、ST、OSC、SOS、PM、APC 和 RIS)!
    【解决方案3】:

    功能

    基于Martijn Pieters♦'s answerJeff's regexp

    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 支持,blablabla)。测试文件应该是这样的:

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

    【讨论】:

    • 为什么在倒数第二个字符集[ -\/] 中留下/ 转义?
    • @AndrewGelnar @ÉdouardLopez [ -/] 就足够了。
    • 我的正则表达式早已扩展为涵盖所有 ANSI C1 代码(7 位),我今天也添加了一个单独的 8 位变体。
    【解决方案4】:

    建议的正则表达式对我没有用,所以我自己创建了一个。 以下是我根据 here 找到的规范创建的 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)
    

    我在以下 sn-p 上测试了我的正则表达式(基本上是从 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) 将终端“转换”为备用字体模式,并在该特定模式下解释下一个字节。该模式下的可能值不限于mnlpz。我什至不会剥离 SS3 之后的字节。 SS2 的功能基本相同(只是字体不同),但您的正则表达式不会拉入下一个字节。
    • 最后但同样重要的是,您的正则表达式实际上未能删除问题示例中的完整 ANSI 代码,因为它留下了 m 最后一个字节。
    【解决方案5】:

    如果它对未来的 Stack Overflowers 有帮助,我会使用 the crayons library 为我的 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))
    

    【讨论】:

    • 虽然这不是同一个问题。 加载不同的库可能会生成包装字符串的自定义对象,我们不需要在此处为​​每个需要在正则表达式处理它们之前转换为字符串的变体提供答案。
    • 这正是我正在寻找的。如果您进行子流程控制,您将获得字节; out.decode("utf-8") 将与引发的 ansi 控制代码冲突:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf7 in position 13894: invalid start byte 并且正则表达式不适用于字节对象。
    【解决方案6】:

    如果要删除\r\n位,可以通过这个函数(written by 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() 选项的翻译也不完全是惯用的(为什么在 for 而不是 xrange() 时使用 while 会这样做,例如 ''.join([chr(i) for i in range(0x20)])),并且不适用于 Python 3(您可以只使用 @987654332 @ 作为string.translate() 映射)。
    【解决方案7】:

    对于 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
    >
    

    【讨论】:

    • 这段代码没有做任何事情:repr(decoded_string) 产生"'ls\\r\\n\\x1b[00m\\x1b[01;31mexamplefile.zip\\x1b[00m\\r\\n\\x1b[01;31m'",而使用\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]) 正则表达式产生"'ls\\r\\nexamplefile.zip\\r\\n'"
    • 原始帖子中没有更改字符串表示的请求。打印或传递给一些 api 方法就足够了
    猜你喜欢
    • 1970-01-01
    • 2011-11-02
    • 1970-01-01
    • 1970-01-01
    • 2020-10-07
    • 1970-01-01
    相关资源
    最近更新 更多