Есть ли безопасная кошка?

Есть ли безопасная кошка?

Время от времени случается, что я загружаю бинарник либо из curl, либо из локальной файловой системы. В большинстве случаев сломанный терминал можно исправить с помощьюперезагрузить. В других случаях, особенно если двоичный файл большой, терминал зависнет на несколько минут, печатая вывод примерно такого вида:

2c1

он же

c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;
2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;
2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;
2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;2c1;

У меня есть три вопроса относительно этого сценария:

  1. Что означает 2c1 и почему терминал это печатает?
  2. Видели ли вы catв дикой природе, как в интерактивном сеансе защищаются от этого нежелательного поведения?
  3. Есть ли у вас какие-либо предложения по программированию такого кота (на CEE или GoLang)?

Моим первым побуждением было обернуть cat в функцию, которая бы это обнаруживала, но вскоре я понял, что это довольно сложно сделать правильно и возникнет множество пограничных случаев.

function cat() {
    # warn user if
    #   - argument 1 is a large  executable 
    #   - argument 1 to the previous command in the a pipe-chain looks like a large binary
    # abort if
    #   - session is interactive and we are able to detect 2c1 garbage
}

Практическим решением может быть всегда использовать less (с LESSPIPE) при просмотре "небезопасного" ввода, но этот вопрос не о пейджерах. Я знаю о less и lesspipe. Я активно использую их каждый день. Возможно less+lesspipeявляется ли решение этой проблемы, что автор(ы) менее реализовали около 20-30 лет назад, столкнувшись с той же проблемой.

Однако, кот отличается от "пейджера" не одним способом... В первую очередь кот неинтерактивен. Для меня это важно.

Предложение о less+lesspipe действительно хорошее (на мой взгляд) с практической точки зрения, но меня больше волнуют тонкости управляющих символов, специальные escape-последовательности и то, как различные терминалы обрабатывают эти входные данные.

Меня больше интересуют технические подробности управляющих символов и то, как терминалы или оболочки интерпретируют "мусор" и управляющие символы. Я не спрашиваю "как бы вы решили эту проблему". Я спрашиваю "почему терминал обрабатывает двоичные файлы таким образом".

решение1

Вместо этого я бы использовал less, который предупреждает о двоичных файлах и на некоторых системах может обрабатывать различные виды (напримерна CentOS 7 я могу это сделать less file.rpmи увидеть файлы в RPM). Я думаю, это называется "lesspipe".

Также, в следующий раз, когда это произойдет, вы можете попробовать resetили tput reset, чтобы вернуться к нормальному состоянию. Они отправляют escape-последовательности на терминал, которые говорят ему сбросить его в нормальное состояние по умолчанию, а также делают эквивалент того, stty saneчто изменяет настройки файла устройства tty на нормальное значение по умолчанию (дамп двоичного файла не должен влиять на них). resetтакже может исправить представление файла устройства tty о размере окна терминала или эмулятора терминала (как сообщает tty size) с теми терминалами, которые поддерживают его запрос.

решение2

Никто не упомянул 'strings'. Хотя strings не совсем похож на cat, он печатает только текстовые строки из непрерывных текстовых данных, чтобы сделать их более безопасными для просмотра в терминале. Обычно он поставляется с пакетом binutils. Это удобная программа для быстрой гарантии того, что вы не получите никакого двоичного вывода из напечатанных данных, а также полезна, если вы просто хотите увидеть непрерывные недвоичные данные. Обратите внимание, что по умолчанию он печатает только непрерывные текстовые разделы с 4 или более символами ASCII. Это можно настроить с помощью параметра -n.

решение3

Вы взаимодействуете с терминалом или эмулятором терминала через последовательную линию или псевдотерминальное устройство (которое эмулирует последовательную линию).

Хотя в ядре есть программный модуль, который находится посередине как своего рода адаптационный слой и выполняет некоторые преобразования (которые будут кратко рассмотрены ниже), обычно вы:

  • отправить поток байтов на терминал по этой последовательной линии, который терминал интерпретирует либо как глифы для отображения на экране, либо как специальные инструкции для изменения своего поведения
  • В ответ терминал отправляет поток байтов на компьютер по другому проводу этой последовательной линии, чтобы сообщить компьютеру, что вы ввели, или ответить на некоторые полученные им управляющие запросы.

Например, терминал может быть настроен как ISO8859-1 (он же latin1 терминал), что означает, что когда он получает 0x53 0x74 0xe9 0x70 0x68 0x61 0x6e 0x65, он интерпретирует его как отображение глифов S, t, é, p, h, a, n, eв текущей позиции курсора на своем экране. И наоборот, когда пользователь вводит S, терминал отправляет байт 0x53.

Значения байтов в диапазоне от 0 до 0x1f интерпретируются какконтрольсимволы. То есть они не представлены в виде глифов, но имеют особое значение.

Например:

  • 0x7 (BEL) сгенерировать аудио- или видеооповещение
  • 0x8 (BS) перемещает курсор влево
  • 0xa (LF) перемещает курсор вниз
  • 0xd (CR) перемещает курсор в первый столбец экрана
  • 0x9 (TAB) перемещает курсор на следующую табуляцию

В этом диапазоне всего 32 управляющих символа, и большинство терминалов имеют гораздо больше функций или способов их управления. Так что помимо них вы можете отправлять последовательности из более чемодинбайт для управления вашим терминалом. Для большинства терминалов и для большинства этих последовательностей первый байт — 0x1b (ESC), за которым следует один или несколько байтов.

Например, хотя есть управляющие символы для перемещения курсора влево или вниз, как показано выше, нет ни одного для перемещения его вправо или вверх (как изначально в телепишущих машинках перемещение вправо осуществлялось с помощью «пробел», но в терминалах с ЭЛТ это стирает то, что находится под курсором, и вы не будете перемещаться вверх на телепишущей машинке, так как это, скорее всего, приведет к замятию бумаги), поэтому для них пришлось ввести управляющие последовательности, на большинстве терминалов 0x1b 0x5b 0x43 и 0x1b 0x5b 0x41 соответственно (кстати, это также последовательность байтов, которую многие терминалы отправляют при нажатии клавиш Rightи Upдля тех, у кого есть такие клавиши).

Среди управляющих последовательностей, поддерживаемых терминалами, есть такие, которые:

  • изменить цвет текста или фона и другие атрибуты графической визуализации
  • изменить набор символов. Например, в latin1 нет греческого символа, а терминалы (со времен до Unicode и до сих пор) поддерживают переключение на другой набор символов для отображения букв других языков или символов рисования рамок.
  • установить положение табуляции
  • может запрашивать информацию из терминала, такую ​​как положение курсора, цвет, заголовок окна, размер...
  • может повлиять на обработку ввода. Например, некоторые терминалы поддерживают вход в режим, при котором при нажатии, например, Shift+ Aотправляется не Aсимвол 0x41 (ASCII), а последовательность байтов, кодирующая информацию о модификаторах (shift, alt, ctrl...) и коде клавиши.
  • некоторые эмуляторы терминала X11 распознают escape-последовательности для изменения шрифта, размера окна, отображения изображений JPEG, отправки содержимого экрана на принтер...

В текстовом файле обычно есть только байты (или последовательности байтов, если это UTF-8 или другие многобайтовые кодировки), представляющие графические символы. ЕдинственныйконтрольСимволы, которые вы найдете в текстовых файлах, — это NL (0xa, он же LF) и TAB (0x9).

Когда вы делаете cat file.txt, catпросто считывает содержимое file.txtи записывает его в свой stdout. Если stdout — это файл последовательного или псевдо-tty устройства ( например) /dev/ttyS0, /dev/pts/0в который вставлена ​​терминальная дисциплина строки, как это было бы в случае, если бы вы запустили эту команду из интерактивной оболочки в эмуляторе терминала, дисциплина строки преобразует эти NL в CR+NL (хотя NLNL может быть преобразован просто в CRNLNL), поэтому терминал при получении CRNL переместит курсор в начало, а затем вниз.

Таким образом, текст в содержимом файла будет отображаться на экране терминала при условии, что текст в файле закодирован в наборе символов терминала.

Байты в исполняемых файлах или других случайных двоичных файлах не предназначены для представления символов, они могут иметь любое значение, включая значения в диапазоне от 0 до 31, поэтому при отправке на терминал терминал будет выполнять то, что ему сказано, и интерпретировать их как управляющие символы, что может заставить его выполнять все, что перечислено выше, и многое другое, что сделает его полностью непригодным для использования.

Чтобы защититься от этого, во-первых, не отправляйте эти файлы на терминал, так как это не имеет смысла, или если вы не знаете, является ли файл текстовым файлом (или файлом, предназначенным для дословного просмотра на терминале с помощью escape-последовательностейнамеревался(для интерпретации терминалом) или нет, вы можете использовать инструмент, который либо удалит управляющие символы (по крайней мере, все, кроме TAB и NL), либо придаст им визуальное графическое представление.

Это то, что делают опции -vи -t, поддерживаемые многими catреализациями. Где с -v, все, кроме NL и TAB, преобразуются в некоторую ^Xнотацию для байтов 0-31 и 0x7f, M-^Xдля байтов 0x80-0x9f и 0xff и M-Xдля байтов 0xa0-0xfe, которые являются общими визуальными представлениями не-ASCII символов. И -tделает это только для TAB (измененного на ^I).

Или вы можете использовать пейджер, такой как lessили vim, viewкоторые делают это по умолчанию (по крайней мере, если вы не используете параметры -r/ -Rraw) и немного умнее в том смысле, что они не преобразуют не-ASCII-символы, которые должны иметь графическое представление в вашей локали, и делают более понятным, какие байты были преобразованы, используя режимы раскрашивания или выделения.

Или вы можете использовать инструменты, предназначенные для предварительного просмотра нетекстовых файлов, таких как hexdump -Cили xxd.

См. также lкоманду , sedкоторая делает что-то похожее cat -vteи является стандартной (в отличие от cat -vte) менее двусмысленным образом:

sed -n l < a-file

решение4

  • Да, юный падаван.
$ cargo search bat 
bat = "0.23.0"            # A cat(1) clone with wings.
  • Но, хозяин, что такое груз?
$ cargo --help |any install rust 
Rust's package manager
      --list                List installed commands
      --explain <CODE>      Run `rustc --explain CODE`
    install     Install a Rust binary. Default location is $HOME/.cargo/bin
    uninstall   Uninstall a Rust binary
$ cargo install bat
(...)

владелец:Это произойдет, если вы попробуете batдвоичный файл.

$ bat `which bat`
[bat warning]: Binary content from file '/home/jaroslav/.cargo/bin/bat'
will not be printed to the terminal (but will be present if the output
of 'bat' is piped). You can use 'bat -A' to show the binary file contents.
  • падаван:Есть ли какие-нибудь недостатки у биты, мастер?
  • владелец:Да, он может быть медленным на больших файлах, но это частично потому, что он делает подсветку структурированного синтаксиса. Это можно отключить, например, --style=plain --color=never
  • падаван:А как насчет странных символов, которые cat выводит на терминал, хозяин?
  • владелец:Это происходит потому, что терминалы с радостью принимают и интерпретируют все, что выглядит как escape-код ANSI (внутренняя команда терминала) и пытаются сделать то, что говорит команда, если эта команда реализована. Для краткого введения, посмотрите этосписок последовательностей цветов ANSI

Вот как воспроизвести это поведение:

$ echo -ne "\u1B\u5B\u63" | xxd
00000000: 1b5b 63                                  .[c

$ echo -ne "\u1B\u5B\u63" 
^[[?1;2c

$ 1;2c

Связанный контент