После долгого отсутствия я вернулся к вам, мои несуществующие читатели, с новой интересной идеей - использованием less
, как фронта для скриптов.
Вы же знаете less
? Это то приложение, которое позволит вам посмотреть длинный файл в консоли, не мучая прокрутку терминала. В нём можно отобразить текст, что-то поискать, прыгнуть туда или даже сюда.
Так почему бы не воспользоваться всем этим богатством функций в наших скриптах, особенно учитывая, что у less
есть и чуть менее известная функциональность. А именно:
lesskey - добавляем интерактивность
У less
есть концепция горячих клавиш, на которые мы назначаем какую-то функциональность из набора встроенных возможностей. Ознакомиться с ними можно в man lesskey
, но из всего богатства нас будет интересовать только возможность запускать shell скрипты прямо из less
.
Биндинги мы будем описывать в обычном текстовом файле, который потом подадим программе на вход, через аргумент командной строки.
Старые версии less (< 582) требовали чтобы файл с биндингами был преобразован в бинарный формат программой lesskey
, но мы будем использовать только новые, причем желательно собранные вручную из исходников. Это будет полезно ещё и потому, что системная версия less
обычно собирается с прицелом на безопасность, и в ней выключена, или ограничена, возможность использования lesskey
. Например, в OpenBSD less
собран без поддержки запуска shell скриптов, а версия из MacOS вообще лишена возможности использовать свои биндинги. Ни на одной из них наш скрипт работать не будет.
Протокол “суслик”
С помощью less
(и небольшого shell скрипта), мы выйдем в интернет и будем посещать там сайты, работающие на протоколе gopher. Это старый протокол, который когда-то был альтернативой www и предназначался для организации иерархичного доступа к документам в сети. Протокол позволяет делать директории, содержащие ссылки на файлы в определенных форматах и другие (саб)директории.
Синтаксис формата очень прост. Каждая запись в директории занимает одну строку, первый символ которой обозначает тип контента, на который она ссылается. Мы будем поддерживать три типа:
- 0 текстовый файл
- 1 другая директория
- i информационное сообщение (это нестандартный, но очень широко используемый тип, позволяющий выводить текст, не являющийся ссылкой, прямо в директории).
Стандарт определяет ещё несколько типов, но большая часть из них устарела и не используется сейчас сколько нибудь широко. Ознакомиться с ним можно в RFC 1436. Интерес представляет тип 7 - позволяющий сослаться на сервис полнотекстового поиска - аналог google, только для гофера. Из известных поисковых сервисов есть Veronica-2, к которой можно получить доступ через веб прокси вот здесь. После типа идет строка, которая говорит пользователю о том, что же он увидит по этой ссылке, а затем, разделенные знаком табуляции, идут хост и урл самой ссылки. У формата до сих пор есть активные поклонники, держащие на нем свои сайты и блоги. Обычно это люди, утомленные современным вебом, наслаждающиеся минимализмом, отсутствием js, трекинга и рекламы.
“Готовое” “решение” - skefir
Наш скрипт представляет собой 63 строки кода, написанного на ANSI Shell (отсутствие башизмов проверял с помощью shellcheck). Он будет принимать на вход сервер и url нужного нам ресурса, скачивать его с помощью nc
и, если это gopher директория, на лету генерировать набор биндингов, позволяющих перейти к документам в ней.
С полным текстом программы можно ознакомиться на gist.github.com, а здесь мы с вами разберем основные моменты, заслуживающие внимания.
LESSKEY_FILE=$(mktemp -t skefir.XXXXXX)
echo "#command" >> "$LESSKEY_FILE"
TEXT=$(printf "%s\r\n" "$2" | nc "$1" 70)
[ -z "${NO_MAP:-}" ] && TEXT="$(echo "$TEXT" | viewer)"
echo "$TEXT" | less --lesskey-src="$LESSKEY_FILE"
rm "$LESSKEY_FILE"
В первой строке мы, с помощью mktemp
, делаем временный файл, в котором будем хранить набор сгенерированных биндингов. При запуске less
мы передадим этот файл с помощью аргумента --lesskey-src
, а после того как пользователь насладится контентом и закроет less
, мы этот файл удалим.
В переменных $1
и $2
у нас лежит хост и адрес контента, к которому мы хотим получить доступ. Протокол говорит, что мы должны открыть TCP соединение с сервером (обычно на 70-ом порту) и отправить туда нужный нам адрес, завершим его символами \r\n
. Для этого мы используем утилиту nc
, в которую передаем подготовленную нами строку-адрес и получаем в ответ весь контент, который нам вернул сервер.
TCP соединение может закрыться до того, как весь контент будет передан и у протокола нет никакой возможности дать нам знать, что мы скачали всё что было нужно (что делает в http заголовок Content-Length, например). Из-за этого некоторые авторы ставят в конце своих постов точку на отдельной строке, чтобы пользователь смог визуально убедиться, что у него скачался весь текст.
Переменная NO_MAP
выставляется в true
, если на вход программе был передан аргумент -n
. Он говорит нам о том, что мы сейчас будем запрашивать текстовый файл, а не директорию с навигацией, и парсить его нам совсем не надо. В таком случае мы просто передадим весь вывод nc
на вход less
.
А вот если парсить все-таки надо, то в ход пойдет функция viewer
, которая переформатирует содержимое в человекочитабельный вид и вызовет link
для генерации тех самых биндингов. Давайте на неё посмотрим.
viewer () {
LINK=0
while read -r LINE || [ -n "$LINE" ]; do
case "$LINE" in
i*)
printf " "
echo "$LINE" | awk -F '\t' '{print substr($1, 2)}'
;;
1*)
link "$LINK" "$LINE" ""
LINK=$((LINK + 1))
;;
0*)
link "$LINK" "$LINE" "-n"
LINK=$((LINK + 1))
;;
*)
;;
esac
done
}
Мы читаем контент гофер директории строка за строкой и, ориентируясь на первый символ в строке, принимаем решение о том, как нам с ней поступить.
Если в начале символ i
, то мы парсим строку с помощью awk
, делим её по знаку табуляции и выводим пользователю только часть до \t
, откусив от этой части первый символ - букву i
. После табуляции идет хост и урл, которые не нужны информационному сообщению, так как оно не является ссылкой, но всё равно часто добавляются гофер серверами для обратной совместимости со старыми клиентами.
Если в начале строки стоит 0 или 1, то мы имеем дело либо с ссылкой на текстовый файл, либо с ссылкой на директорию. И для того и для того нам нужно сгенерировать кейбиндинг, который будет запускать новый инстанс skefir
в текущем терминале. Единственная разница заключается в том, что для текстового файла мы будем прокидывать аргумент -n
, говорящий скрипту, что парсить контент как директорию не нужно.
link () {
SHORTCUT=$(echo "$1" | base64 | tr -d '=')
LINE=$2
ARGS=$3
printf "[%5s] " "$SHORTCUT"
echo "$LINE" | awk -F '\t' '{print substr($1, 2)}'
KEFIR_CALL="$(echo "$LINE" | awk -F '\t' '{printf "%s %s", $3, $2}')"
COMMAND="$SHORTCUT shell skefir $ARGS $KEFIR_CALL"
printf "%s\n" "$COMMAND" >> "$LESSKEY_FILE"
}
Изначально я помечал ссылки цифровыми шорткатами и, для первых десяти ссылок, это работало очень удобно. К сожалению, обработка горячих клавиш в less
реализована таким образом, что при наличие в списке 1
и, например, 14
, он при вводе всегда будет сразу вызывать первый шорткат, не дожидаясь окончания ввода числа. Для текстовых команд такого не происходит и я не придумал ничего лучше, чем преобразовывать цифры в буквы через base64
.
В функции link
мы готовим биндинг в формате
MAo shell skefir gopher.club /phlogs/
где MAo
это кейбиндинг, shell
означает, что мы хотим выполнить команду в терминале, ну а в конце идет эта самая команда, сформированная на основе просмторенной гофер директории. Теперь мы, просматривая гофер директорию в less
, можем набрать MAo
и открыть новый less
с новым набором биндингов, который уже позволит нам передвигаться по новой директории.
Чтобы путешествовать по истории открытых ресурсов назад мы будем просто закрывать новые инстансы less
. Ну а путешествие вперед у нас не реализовано.
Я выложил на asciinema запись работы skefir
- посмотрите, если интересно. Можно заметить, как долго открывается вторая страница - это огромный документ со списком всех пользователей, которые когда-либо делали гофер блоги на sdf.org и мы должны получить и спарсить его целиком до начала рендеринга. В нормальном клиенте, мы бы начали выводить контент до полного парсинга, но мы нормальный тут и не делали.
Выводы
Получилось интересно, но не очень практично. Для комфортного путешествия по сусликам есть куда более классные клиенты с интересными идеями и качественным исполнением, взять хотя бы VF-1.
Но свой потенциал у такого использования less
все-таки есть. Например, если вам нужно просмотреть кучу файлов и выполнить над ними одно из нескольких типовых действий - то less
с кастомными кейбиндингами может упростить вам жизнь.