Как удалить escape-последовательности ANSI из строки в python
Это моя строка:
'ls\r\n\x1b[00m\x1b[01;31mexamplefile.zip\x1b[00m\r\n\x1b[01;31m'
Я использовал код для извлечения вывода из SSH-команды, и я хочу, чтобы моя строка содержала только "examplefile.zip"
Что я могу использовать для удаления дополнительных escape-последовательностей?
Ответы
Ответ 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, что позволит избежать конкатенации нежелательного бита.