Переносим философию Unix в 21 век

Kate

Administrator
Команда форума
Один из основных принципов философии Unix заключается в создании таких программ, каждая из которых эффективно выполняет всего одну задачу, и связывании этих программ в конвейер. Подобный подход отлично зарекомендовал себя за десятилетия существования системы.

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

Впервые философия Unix была официально сформулирована в предисловии к научному журналу Bell Systems Technical Journal 1978 года, где описывалась система с разделением времени:

nkwrmqlvwb9lpequ91yu4ievl6c.png


Предисловие к Bell System Technical Journal

Среди создателей и пользователей системы Unix выработался особый набор принципов, описывающий и продвигающий ее отличительный стиль:

(i) Добиваться от одной программы эффективного выполнения одной задачи. Для реализации других лучше создать отдельную программу, чем нагружать новыми «возможностями» старые.

(ii) Вывод каждой программы должен становиться вводом для другой. Не засорять вывод посторонней информацией. Избегать строго колоночных или двоичных форматов входных данных. Не стремиться к интерактивному вводу.

(iii) Проектировать и создавать программы, в том числе операционные системы, с возможностью раннего тестирования, желательно в течение нескольких недель. Не сомневаясь отбрасывать все непроработанные элементы и заново их переделывать.

(iv) Предпочитать неквалифицированной помощи использование инструментов, чтобы…

Пункты i и ii часто повторяются в современном мире разработки, и не с проста. Но теперь пришло время перенести эту философию в 21 век, определив стандартный формат вывода для не интерактивного использования.

К сожалению, если сегодня мы хотим получить IP-адрес одного из Ethernet-интерфейсов в системе Linux, то дела обстоят так:

$ ifconfig ens33 | grep inet | awk '{print $2}' | cut -d/ -f1 | head -n 1

Совсем не радует глаз.

Примерно до 2013 года считалось, что неструктурированный текст является хорошим способом вывода данных в командной строке. В Unix/Linux есть много инструментов парсинга текста, таких как sed, awk, grep, tr, cut, rev и так далее, которые можно объединить в конвейер для переформатирования нужных данных перед их отправкой следующей программе.

Конечно же, это всегда вызывало боль и поднимало много вопросов о том, как парсить вывод такой-то программы. Требование парсить неструктурированные (лишь иногда понятные человеку) данные вручную излишне усложняло жизнь обычных системных администраторов.

Однако в 2013 году появился конкретный формат данных, получивший название JSON, который сначала был стандартизирован как ECMA-404, а позднее в 2017 году как RFC 8259 и ISO/IEC 21778:2017. На сегодня JSON повсеместно встречается в REST API и используется для сериализации всего, начиная с данных между приложениями до индикаторов компрометации в спецификации STIX2 и заканчивая файлами конфигурации.

Во всех современных языках присутствуют библиотеки JSON и даже инструменты парсинга этого формата для командной строки, например jq. JSON повсюду. Его легко использовать, и он является стандартом.

Если бы JSON существовал в 1970 году, когда я только родился, то Кен Томпсон и Деннис Ричи вполне могли бы определить его в качестве рекомендованного формата вывода, чтобы помочь программам в конвейере «выполнять эффективно одну задачу».

В этой связи, я утверждаю, что Linux и все его поддерживаемые GNU и не GNU утилиты должны предоставлять вариант вывода JSON. Мы уже наблюдаем ограниченную поддержку этого в тех же утилитах systemctl или ip, где на выходе этот формат можно получить через опцию -j.

Проблема в том, что многие дистрибутивы Linux пока не включают версию, предоставляющую возможность вывода JSON (например, centOS). И даже, если такая возможность есть, подобный вывод поддерживается не всеми функциями.

Примеры ниже:

Вот ip addr с выводом JSON:

$ ip -j addr show dev ens33
[{
"addr_info": [{},{}]
},{
"ifindex": 2,
"ifname": "ens33",
"flags": ["BROADCAST","MULTICAST","UP","LOWER_UP"],
"mtu": 1500,
"qdisc": "fq_codel",
"operstate": "UP",
"group": "default",
"txqlen": 1000,
"link_type": "ether",
"address": "00:0c:29:99:45:17",
"broadcast": "ff:ff:ff:ff:ff:ff",
"addr_info": [{
"family": "inet",
"local": "192.168.71.131",
"prefixlen": 24,
"broadcast": "192.168.71.255",
"scope": "global",
"dynamic": true,
"label": "ens33",
"valid_life_time": 1732,
"preferred_life_time": 1732
},{
"family": "inet6",
"local": "fe80::20c:29ff:fe99:4517",
"prefixlen": 64,
"scope": "link",
"valid_life_time": 4294967295,
"preferred_life_time": 4294967295
}]
}
]

А вот ip route, не выводящий JSON, даже с флагом -j:

$ ip -j route
default via 192.168.71.2 dev ens33 proto dhcp src 192.168.71.131 metric 100
192.168.71.0/24 dev ens33 proto kernel scope link src 192.168.71.131
192.168.71.2 dev ens33 proto dhcp scope link src 192.168.71.131 metric 100

Некоторые другие более современные инструменты вроде kubectl и aws-cli предлагают более согласованные варианты вывода JSON, упрощая парсинг и составление конвейера.

Но есть и много старых инструментов, которые до сих пор выводят практически недоступный для парсинга текст (например, netstat, lsblk, ifconfig, iptables и т.д.).

Интересно, что в Windows PowerShell реализовали использование структурированных данных, что является хорошим примером, на котором сообщество Linux может поучиться.

Предложения по решению​


Решением будет вернуться ко всем GNU и не-GNU легаси-утилитам командной строки, выводящим текстовые данные, и добавить к ним вариант вывода JSON. Все API операционной системы, такие как /proc и /sys, должны сериализовывать свои файлы в JSON или передавать данные альтернативному API, выводящему их в этом формате.

_bgjap__bc0v7wbfcg8_kyozrj0.png


github.com/kellyjonbrazil/jc

Тем временем я написал инструмент под названием jc, который конвертирует вывод десятков как GNU, так и не GNU команд, а также файлов конфигурации в JSON.

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



JC в действии​


Ниже я продемонстрирую, как jc может упростить нам жизнь, пока GNU/Linux не привнесут философию Unix в реалии 21 века. Возьмем тот же пример с получением IP-адреса из сети Ethernet:

$ ifconfig ens33 | grep inet | awk '{print $2}' | cut -d/ -f1 | head -n 1
192.168.71.138

А вот, как то же самое делается с помощью jc и инструмента парсинга вроде jq:

$ ifconfig ens33 | jc --ifconfig | jq -r '.[].ipv4_addr'
192.168.71.138

Либо:

$ jc ifconfig ens33 | jq -r '.[].ipv4_addr'
192.168.71.138

Вот еще один пример перечисления прослушиваемых TCP-портов системы:

$ netstat -tln | tr -s ' ' | cut -d ' ' -f 4 | rev | cut -d : -f 1 | rev | tail -n +3
25
22

Очень много текста для простого получения портов. Вот то же самое, но с использованием jc и jq:

$ netstat -tln | jc --netstat | jq '.[].local_port_num'
25
22

Либо:

$ jc netstat -tln | jq '.[].local_port_num'
25
22

Обратите внимание, насколько более интуитивны поиск и сравнение семантически-улучшенных структурированных данных относительно неудобного парсинга низкоуровневого текста.

Кроме того, вывод JSON можно сохранить для использования любым языком высокого уровня вроде Python или JavaScript без парсинга командной строки. Это наше будущее, друзья!

Сейчас jc поддерживает следующие парсеры: arp, df, dig, env, free, /etc/fstab, history, /etc/hosts, ifconfig, iptables, jobs, ls, lsblk, lsmod, lsof, mount, netstat, ps, route, ss, stat, systemctl, systemctl list-jobs, systemctl list-sockets, systemctl list-unit-files, uname -a, uptime и w.

Помимо этого, в jc появилась поддержка множества программ и типов файлов.

 
Сверху