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

2024-12-20 08:38:00
admin
原创
139
摘要:问题描述:这是包含我的字符串的片段。'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
>
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用