Подтвердить что ты не робот

Как удалить escape-последовательности ANSI из строки в python

Это моя строка:

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

Я использовал код для извлечения вывода из SSH-команды, и я хочу, чтобы моя строка содержала только "examplefile.zip"

Что я могу использовать для удаления дополнительных escape-последовательностей?

4b9b3361

Ответ 1

Удалите их с помощью регулярного выражения:

import re

# 7-bit C1 ANSI sequences
ansi_escape = re.compile(r'''
    \x1B    # ESC
    [@-_]   # 7-bit C1 Fe
    [0-?]*  # Parameter bytes
    [ -/]*  # Intermediate bytes
    [@-~]   # Final byte
''', re.VERBOSE)
result = ansi_escape.sub('', sometext)

или без флага VERBOSE в сжатом виде:

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

Demo:

>>> import re
>>> ansi_escape = re.compile(r'\x1B[@-_][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-битные escape-последовательности ANSI C1, но не 8-битные открыватели escape-последовательности 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
        \x1B
        [@-_]
    |   # or a single 8-bit byte Fe
        [\x80-\x9F]
    )
    [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[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
result = ansi_escape_8bit.sub(b'', somebytesvalue)

Для получения дополнительной информации см.:

Приведенный вами пример содержит 4 кода CSI (Control Sequence Introducer), отмеченных начальными байтами \x1B[ или ESC [, и каждый из них содержит код SGR (Select Graphic Rendition), поскольку каждый из них заканчивается на m. Параметры (разделенные точкой с запятой ;) указывают терминалу, какие атрибуты графического представления использовать. Таким образом, для каждой последовательности \x1B[....m используются 3 кода:

  • 0 (или 00 в этом примере): сброс, отключение всех атрибутов
  • 1 (или 01 в примере): полужирный
  • 31: красный (на переднем плане)

Однако в ANSI есть нечто большее, чем просто коды CSI SGR. Только с помощью CSI вы также можете управлять курсором, очисткой линий или всего дисплея или прокруткой (если, конечно, терминал поддерживает это). Кроме CSI, есть коды для выбора альтернативных шрифтов (SS2 и SS3), для отправки "личных сообщений" (например, паролей), для связи с терминалом (DCS), ОС (OSC) или само приложение (APC, способ применения приложениями для добавления пользовательских кодов управления в поток связи) и дополнительные коды, помогающие определить строки (SOS, Start of String, ST String Terminator) или вернуть все обратно в базовое состояние (RIS). Вышеуказанные регулярные выражения охватывают все это.

Ответ 2

Принятый ответ на этот вопрос учитывает только эффекты цвета и шрифта. Существует множество последовательностей, которые не заканчиваются на "m", такие как расположение позиционирования курсора, стирание и прокрутка.

Полное регулярное выражение для контрольных последовательностей (aka ANSI Escape Sequences) -

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

Обратитесь к ECMA-48 Раздел 5.4 и ANSI escape-код

Ответ 3

Функция

На основании Мартина Питерса ♦ ответьте with Джеффом регулярным выражением.

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

Test

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 (лучшая поддержка юникода, блаблабла). Вот как должен выглядеть тестовый файл:

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

Надеюсь, это поможет другим :)

Ответ 5

Если это поможет будущим переполнениям стека, я использовал библиотеку crayons, чтобы придать выводу Python немного больше визуального эффекта, что является преимуществом, поскольку оно работает на платформах Windows и Linux. Однако я отображал как на экране, так и добавлял файлы журналов, а escape-последовательности влияли на читаемость файлов журналов, поэтому я хотел удалить их. Однако escape-последовательности, вставленные мелками, выдают ошибку:

expected string or bytes-like object

Решением было преобразовать параметр в строку, поэтому потребовалась лишь небольшая модификация общепринятого ответа:

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

Ответ 6

если вы хотите удалить бит \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

Осторожно, однако, это объединит текст впереди и за escape-последовательностями. Итак, используя отфильтрованную строку Martijn 'ls\r\nexamplefile.zip\r\n', вы получите lsexamplefile.zip. Обратите внимание на ls перед желаемым именем файла.

Сначала я использовал бы функцию stripEscape, чтобы удалить escape-последовательности, а затем передать результат в регулярное выражение Martijn, что позволит избежать конкатенации нежелательного бита.