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

2024-12-20 08:38:00
admin
原创
61
摘要:问题描述:这是包含我的字符串的片段。'ls x1b[00mx1b[01;31mexamplefile.zipx1b[00m x1b[01;31m' 该字符串是我执行的 SSH 命令返回的。我无法使用当前状态下的字符串,因为它包含 ANSI 标准化转义序列。我如何以编程方式删除转义序列,以便字符串仅剩下部分'e...

问题描述:

这是包含我的字符串的片段。
'ls
x1b[00mx1b[01;31mexamplefile.zipx1b[00m
x1b[01;31m'

该字符串是我执行的 SSH 命令返回的。我无法使用当前状态下的字符串,因为它包含 ANSI 标准化转义序列。我如何以编程方式删除转义序列,以便字符串仅剩下部分'examplefile.zip'


解决方案 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
x1b[00mx1b[01;31mexamplefile.zipx1b[00m
x1b[01;31m'
>>> ansi_escape.sub('', sometext)
'ls
examplefile.zip
'

上述正则表达式涵盖所有 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-x9Ax9C-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-x9Ax9C-x9F]|(?:x1B[|x9B)[0-?]*[ -/]*[@-~])'
)
result = ansi_escape_8bit.sub(b'', somebytesvalue)

有关详细信息,请参阅:For more information, see:

  • 维基百科上的ANSI转义代码概述

  • ECMA-48 标准,第 5 版(特别是第 5.3 和 5.4 节)

您给出的示例包含 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:

接受的答案仅考虑了格式化为改变前景色和文本样式的 ANSI 标准化转义序列。许多序列不以 结尾'm',例如:光标定位、擦除和滚动区域。下面的模式试图涵盖除设置前景色和文本样式之外的所有情况。

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

其他参考资料:
  • ECMA-48 第 5.4 节

  • ANSI 转义码

解决方案 3:

功能

根据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 = '    /u001b[0;35mBlabla/u001b[0m                                  /u001b[0;36m172.18.0.2/u001b[0m'

    escaped_line = escape_ansi(line)

    self.assertEqual(escaped_line, '    Blabla                                  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()

解决方案 4:

建议的正则表达式对我来说不起作用,所以我自己创建了一个。以下是我根据此处的规范创建的 Python 正则表达式

ansi_regex = r'x1b(' \n             r'([??d+[hl])|' \n             r'([=<>a-kzNM78])|' \n             r'([()][a-b0-2])|' \n             r'([d{0,2}[ma-dgkjqi])|' \n             r'([d+;d+[hfy]?)|' \n             r'([;?[hf])|' \n             r'(#[3-68])|' \n             r'([01356]n)|' \n             r'(O[mlnp-z]?)|' \n             r'(/Z)|' \n             r'(d+)|' \n             r'([?d;d0c)|' \n             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

希望这可以帮助其他人:)

解决方案 5:

在我使用OSC 序列的情况下,所有正则表达式解决方案均不起作用( x1b])

要实际呈现可见的输出,你需要一个像pyte这样的终端仿真器

#! /usr/bin/env python3

import pyte # terminal emulator: render terminal output to visible characters

pyte_screen = pyte.Screen(80, 24)
pyte_stream = pyte.ByteStream(pyte_screen)

bytes_ = b''.join([
  b'$ cowsay hello
', b'x1b[?2004l', b'
', b' _______
',
  b'< hello >
', b' -------
', b'        \\   ^__^
',
  b'         \\  (oo)\\_______
', b'            (__)\\       )\\/\\
',
  b'                ||----w |
', b'                ||     ||
',
  b'x1b]0;user@laptop1:/tmpx1b\\', b'x1b]7;file://laptop1/tmpx1b\\', b'x1b[?2004h$ ',
])
pyte_stream.feed(bytes_)

# pyte_screen.display always has 80x24 characters, padded with whitespace
# -> use rstrip to remove trailing whitespace from all lines
text = ("".join([line.rstrip() + "
" for line in pyte_screen.display])).strip() + "
"
print("text", text)

print("cursor", pyte_screen.cursor.y, pyte_screen.cursor.x)
print("title", pyte_screen.title)

解决方案 6:

如果这对未来的 Stack Overflow 用户有帮助,我会使用crayons 库来让我的 Python 输出更具视觉冲击力,这很有利,因为它可以在 Windows 和 Linux 平台上运行。但是,我既要在屏幕上显示,又要将其附加到日志文件中,转义序列会影响日志文件的可读性,因此我想将其删除。但是,crayons 插入的转义序列产生了错误:

expected string or bytes-like object

解决方案是将参数转换为字符串,因此只需要对普遍接受的答案进行微小的修改:

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

解决方案 7:

当我使用 pexect 时的情况。

child.sendline("ls backup")
child.expect(r"[0-9]{8}_[0-9]{4}.*")
print(child.after.split()) 
-> NG  ['20231016_1603x1b[0m', 'x1b[01;34m20231016_1606x1b[0m']

* Add ls option
child.sendline("ls  --color=never backup")
child.expect(r"[0-9]{8}_[0-9]{4}.*")
print(child.after.split()) 
-> OK!  ['20231016_1603', '20231016_1606']

解决方案 8:

与正则表达式不同的是,此函数迭代字符串(更具体地说是流io.StringIO)的字节以搜索CSI ,并“快进”直到找到x1b最后一个字节。该函数将返回所有剩余字符。更具体地说,将其作为生成器产生。'm'

这仅适用于 ANSI颜色序列

代码:

import io

def strip_ansi_colour(text: str) -> iter:
    """Strip ANSI colour sequences from a string.

    Args:
        text (str): Text string to be stripped.

    Returns:
        iter[str]: A generator for each returned character. Note,
        this will include newline characters.

    """
    buff = io.StringIO(text)
    while (b := buff.read(1)):
        if b == 'x1b':
            while (b := buff.read(1)) != 'm': continue
        else:
            yield b

使用 OP 字符串的示例:

>>> s = 'ls
x1b[00mx1b[01;31mexamplefile.zipx1b[00m
x1b[01;31m'
>>> ''.join(strip_ansi_colour(s))[2:].strip()  # Trim ls and newlines

'examplefile.zip'

示例 2:

>>> s = 'x1b[93;mx1b[40;mx1b[22;m
Foo, spam and eggs.x1b[0m
'
>>> ''.join(strip_ansi_colour(s))

'
Foo, spam and eggs.
'

解决方案 9:

如果您想删除该`
`位,您可以通过此函数传递字符串(由 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
examplefile.zip
',您将得到lsexamplefile.zip。请注意ls`所需文件名前面的。

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

解决方案 10:

对于 2020 年的 Python 3.5 来说,string.encode().decode('ascii')

ascii_string = 'ls
x1b[00mx1b[01;31mexamplefile.zipx1b[00m
x1b[01;31m'
decoded_string = ascii_string.encode().decode('ascii')
print(decoded_string) 

>ls
>examplefile.zip
>
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   984  
  在项目管理领域,CDCP(Certified Data Center Professional)认证评审是一个至关重要的环节,它不仅验证了项目团队的专业能力,还直接关系到项目的成功与否。在这一评审过程中,沟通技巧的运用至关重要。有效的沟通不仅能够确保信息的准确传递,还能增强团队协作,提升评审效率。本文将深入探讨CDCP...
华为IPD流程   0  
  IPD(Integrated Product Development,集成产品开发)是一种以客户需求为核心、跨部门协同的产品开发模式,旨在通过高效的资源整合和流程优化,提升产品开发的成功率和市场竞争力。在IPD培训课程中,掌握关键成功因素是确保团队能够有效实施这一模式的核心。以下将从五个关键成功因素展开讨论,帮助企业和...
IPD项目流程图   0  
  华为IPD(Integrated Product Development,集成产品开发)流程是华为公司在其全球化进程中逐步构建和完善的一套高效产品开发管理体系。这一流程不仅帮助华为在技术创新和产品交付上实现了质的飞跃,还为其在全球市场中赢得了显著的竞争优势。IPD的核心在于通过跨部门协作、阶段性评审和市场需求驱动,确保...
华为IPD   0  
  华为作为全球领先的通信技术解决方案提供商,其成功的背后离不开一套成熟的管理体系——集成产品开发(IPD)。IPD不仅是一种产品开发流程,更是一种系统化的管理思想,它通过跨职能团队的协作、阶段评审机制和市场需求驱动的开发模式,帮助华为在全球市场中脱颖而出。从最初的国内市场到如今的全球化布局,华为的IPD体系在多个领域展现...
IPD管理流程   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用