如何在Python中去除字符串中的ANSI转义序列

141 投票
10 回答
119947 浏览
提问于 2025-04-17 14:44
这里有一段包含我字符串的代码。
'ls\r\n\x1b[00m\x1b[01;31mexamplefile.zip\x1b[00m\r\n\x1b[01;31m'

这个字符串是我执行一个SSH命令后得到的。现在这个字符串的状态不太好,因为里面有一些ANSI标准的转义序列。我想知道怎么才能通过编程的方式把这些转义序列去掉,只留下'examplefile.zip'这一部分。

10 个回答

45

函数

这个内容是基于Martijn Pieters♦的回答Jeff的正则表达式

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()
54

被接受的答案只考虑了ANSI标准的转义序列,这些序列主要用于改变前景颜色和文本样式。其实还有很多序列并不是以'm'结尾的,比如光标定位、擦除和滚动区域等。下面的模式试图涵盖所有除了设置前景颜色和文本样式之外的情况。


以下是ANSI标准控制序列的正则表达式:
/(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]/


其他参考资料:
257

用正则表达式删除它们:

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位的序列几乎不再使用,因为同样的字节范围有了不同的含义。

如果你确实需要处理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代码之间发送的字符串)。要移除那些内容,需要额外的工作,这超出了这个回答的范围。

撰写回答