- PVSM.RU - https://www.pvsm.ru -
Сегодня я хочу рассказать о том, как командные оболочки zsh и fish обнаруживают пропущенные символы перевода строки и выделяют соответствующие места в выводе программ, делая это в условиях, когда модель программирования Unix не даёт им возможности исследовать то, что выводят программы.
Большинство командных оболочек, включая bash, ksh, dash и ash, выводят приглашение командной строки в той позиции, в которой остался курсор после завершения работы предыдущей команды.
То, что приглашение (почти) всегда выводится в известном всем месте, в самой левой колонке следующей строки, объясняется тем фактом, что Unix-программы единодушно сотрудничают в деле размещения курсора именно в этой позиции после завершения их работы.
Делается это благодаря тому, что в конце того, что выводит программа, всегда ставится символ перевода строки n (известный так же как «новая строка»):
vidar@vidarholen-vm2 ~ $ whoami
vidar
vidar@vidarholen-vm2 ~ $ whoami | hexdump -c
0000000 v i d a r n
Если программа не сможет выполнить это соглашение, то приглашение командной строки после этого окажется не там, где обычно:
vidar@vidarholen-vm2 ~ $ echo -n "hello world"
hello worldvidar@vidarholen-vm2 ~ $
Но недавно я заметил, что оболочки zsh и fish в подобных ситуациях выводят особые символы, указывающие на место, в котором должен был стоять знак перевода строки, и, всё равно, показывают приглашение там, где пользователи ожидают его увидеть:
vidarholen-vm2% echo -n "hello zsh"
hello zsh%
vidarholen-vm2%
vidar@vidarholen-vm2 ~> echo -n "hello fish"
hello fish⏎
vidar@vidarholen-vm2 ~>
Если сейчас вы несколько разочарованы тем, что материал, который вы читаете, посвящён такой вот мелочи, то это, скорее всего, значит, что вы никогда не пытались написать собственную командную оболочку. И то, о чём тут идёт речь, представляет собой одну из таких проблем, которые кажутся тем сложнее, чем больше о них узнаёшь.

Взято отсюда [2]
Если вам в голову пришло какое-то простое решение этой задачи — возможно, нечто в духе if (!output.ends_with(»n»)) printf(»%n»); — примите во внимание следующие ограничения:
Учитывая это — вот несколько несовершенных способов решения вышеописанной задачи:
whoami, это и сработает, некоторые программы проверяют, представлен ли файл stdout терминалом [3], и соответствующим образом меняют поведение, а другие идут в обход и общаются с TTY напрямую (например — приглашение ввода пароля, выводимое ssh). Некоторые программы используют особые вызовы ioctl TTY и не будут работать в том случае, если им придётся выводить результаты не в TTY. Например — это происходит при запросе размеров окна или при подавлении вывода на экран введённых символов при вводе пароля.ptrace для того чтобы выяснить, что и куда он записывает. Это означает огромную дополнительную нагрузку на систему и нарушает работу sudo, ping и других команд, полагающихся на suid.ssh или script. Это — муторный и неуклюжий подход, при применении крайней формы которого потребуется переписать весь эмулятор терминала.printf 'e[6n', выполненная в терминале, поддерживающем эту возможность, приведёт к тому, что терминал будет воспроизводить пользовательский ввод в форме ^[[y;xR, где y и x — это строка и столбец. А командная оболочка может читать эти данные для того чтобы иметь представление о том, где находится курсор. Подобные вещи, хотя и выполнимы, отличаются достаточно сложной и заковыристой реализацией, если вспомнить, что речь идёт о наделении командной оболочки сравнительно простой возможностью.В командных оболочках zsh и fish используется подход, который гораздо проще и разумнее тех, о которых мы только что говорили:
$COLUMN-1.Это — очень простое решение, так как для его реализации нужно лишь выводить строку фиксированного размера перед каждым приглашением, но оно чрезвычайно эффективно во всех терминалах. (Разработчик fish и читатель Hacker News ComputerGuru объяснили [4], что в деле переноса строк в разных терминалах есть множество «подводных камней», которые усложняют приведённую здесь схему работы.)
Почему?
Представим, что наш терминал имеет ширину, равную 10 колонкам и высоту в 3 строки. Обычная программа, выводящая короткую строку, выведет в конце и символ перевода строки:
[vidar ]
[| ]
[ ]
Курсор, показанный символом |, находится в начале следующей строки. А вот что произойдёт на шаге 1 и 2:
[vidar ]
[% |]
[ ]
Мы видим индикатор, и так как мы вывели символы, количество которых точно соответствует $COLUMN, курсор находится после последнего столбца. Теперь, на шаге 3, символ возврата каретки перемещает курсор обратно к началу строки:
[vidar ]
[|% ]
[ ]
После этого приглашение командной строки перекрывает индикатор, выводясь в той же строке:
[vidar ]
[~ $ | ]
[ ]
Итоговый результат будет таким же, как если бы мы просто вывели приглашение там, где был курсор.
А теперь давайте посмотрим на то, что произойдёт, если программа не выводит в конце выводимой ей строки символ перевода строки:
[vidar| ]
[ ]
[ ]
Снова выводится индикатор, но в этот раз пробелы, выведенные на шаге 2, приводят к тому, что символы переходят на следующую строку:
[vidar% ]
[ | ]
[ ]
Команда возврата каретки переводит курсор обратно к началу следующей строки:
[vidar% ]
[| ]
[ ]
Теперь приглашение выводится в той же строке и, в результате, не перезаписывает символ индикатора:
[vidar% ]
[~ $ | ]
[ ]
Вот, что у нас получилось. Задача, которая, на первый взгляд, кажется простой, оказалась куда сложнее, чем ожидалось, но разумное использование переноса строк позволило найти простое решение для неё.
Теперь, когда мы знаем о «секретном ингредиенте», мы, конечно, можем сделать то же самое и в bash:
PROMPT_COMMAND='printf "%%%$((COLUMNS-1))s\r"'
Надо отметить, что те же ограничения, о которых мы говорили в этой статье, применимы и к некоторым другим аспектам Unix:
Случалось ли вам браться за решение сложных задач, которые, на первый взгляд, кажутся весьма простыми?
Автор: ru_vds
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/363958
Ссылки в тексте:
[1] Image: https://habr.com/ru/company/ruvds/blog/554292/
[2] отсюда: https://xkcd.ru/1425/
[3] терминалом: https://www.vidarholen.net/contents/blog/?p=22
[4] объяснили: https://news.ycombinator.com/item?id=23520607
[5] Image: http://ruvds.com/ru-rub?utm_source=habr&utm_medium=perevod&utm_campaign=prostoj__no_umnyj_xod_primenyaemyj_v_zsh_i_fish_dlya_resheniya_problemy_propushhennogo_simvola_perevoda_stroki
[6] Источник: https://habr.com/ru/post/554292/?utm_source=habrahabr&utm_medium=rss&utm_campaign=554292
Нажмите здесь для печати.