Содержание статьи
В простейшем случае файл содержит одно значение (токен) на строку — так обычно построены вордлисты для брута или фаззинга. В других случаях файл имеет заданную регулярную структуру, например всевозможные вариации CSV, хорошо подходящие для табличных данных, телеметрии и подобных наборов.
Хуже всего, когда входной файл изначально предназначен для чтения человеком и содержит дополнительное форматирование — отступы, выравнивание пробелами, подзаголовки, псевдографику. Такие файлы мы не можем просто читать «как есть» — приходится находить и использовать только отдельные фрагменты.
В этой статье мы рассмотрим основные приемы получения интересующей нас информации из текстового файла, то есть его парсинг.
Практический пример возьмем несколько отвлеченный, но зато имеющийся на любом компьютере — кеш ARP. Будем использовать данные из этого кеша, чтобы установить соответствие между IP- и MAC-адресами устройств в локальной сети.
Поскольку в разных ОС доступ к содержимому кеша получают немного по‑разному, одновременно разберем и работу с платформенно зависимым кодом: в Linux можно прочитать содержимое /, а в Windows — выполнить arp . Наконец, преобразуем написанный нами код парсера в пакет, пригодный для использования в других проектах.
info
Кеш ARP наполняется операционной системой автоматически (обычно при попытке установки соединения с узлом локальной сети). Доступ к этим сведениям не требует повышения полномочий, в отличие от открытия сокета и отправки ARP discovery пакета вручную (а чтобы сделать такое на Windows-хосте, обычно требуется еще и установить предварительно Npcap/WinPcap).
Начнем, как всегда, с создания нового пакета:
mkdir readarp && cd $_go mod init readarp
touch main.go
touch arp.go
Поскольку часть кода будет зависеть от целевой платформы, сразу создадим два файла. В main. будет наша точка входа, а в arp. — код для работы с кешем ARP.
В будущем мы собираемся получать MAC-адрес, соответствующий заданному IP. В таком сценарии нецелесообразно читать все содержимое кеша при каждом запросе. Гораздо эффективнее сделать это один раз и сразу сохранить интересующие нас сведения для последующих обращений.
Для хранения обработанных данных мы могли бы создать структуру с двумя полями — IP и MAC — и сохранить набор ее экземпляров в слайсе, в котором потом будем искать нужное. Но лучше применить другой подход и хранить данные в виде коллекции пар ключ — значение, где IP-адреса будут уникальными ключами, для чего мы используем структуру данных map.
Хотя пока мы ничего такого не делаем, решение о выборе структуры данных принимаем уже сейчас, чтобы потом не переписывать много кода.
Содержимое файла arp.:
package mainfunc retrieveArpTable() map[string]string { result := make(map[string]string) // TODO not implemented return result}Здесь мы объявляем функцию, которая обрабатывает данные из кеша и возвращает словарь с нужными нам значениями.
И содержимое файла main.:
package mainimport "fmt"func main() { arpResuls := retrieveArpTable() fmt.Print(arpResuls)}Здесь мы просто выводим на экран данные из словаря, сформированного функцией retrieveArpTable(. Пока это не совсем то, что мы хотим сделать в итоге: это просто функция‑заглушка, дающая нам возможность увидеть промежуточные результаты выполнения. Чуть позже мы от нее вообще избавимся.
Обрати внимание: хоть файлы и разные, они относятся к одному пакету — в данном случае main, — и мы в файле main. без каких‑либо дополнительных действий вызываем функцию, определенную в arp..
Отличие структуры от структуры
Возможно, к этому моменту у тебя в голове возникла некоторая путаница. Раньше мы говорили о структуре как о типе данных, который объявляем с ключевым словом struct и далее работаем с его полями. Но также мы используем такое выражение, как «структура данных», говоря о map или slice.
Действительно, и map, и slice — внутри структура с соответствующими полями и методами. Но дело здесь в другом. В computer science «структура данных» — это фундаментальное понятие, описывающее конкретный способ размещения и организации коллекции данных в памяти.
Структуры данных — это такие вещи, как списки, стеки, очереди, деревья, хеш‑таблицы и прочее. А реализованы они могут быть с помощью структур (которые struct), или классов, или еще как‑нибудь. В свою очередь, тип (type ), являющийся реализацией той или иной структуры данных, мы в обиходе обычно тоже называем просто структурой данных, то есть здесь имеет место некоторое размывание границ между понятиями.
Словари, или карты (map)
Функция retrieveArpTable( возвращает словарь (ключевое слово map) с ключами типа string (тип ключа указывается в квадратных скобках) и значениями типа string (тип значения указывается после квадратных скобок).
info
Map — это коллекция пар ключ — значение, где ключи уникальны и применяются для извлечения значений, подобно тому как в массивах или срезах используется индекс. В разной литературе по разным языкам программирования встречаются разные названия этой структуры: «ассоциативный массив», «словарь», «хеш‑таблица», дословный перевод «карта» и даже калька с английского «мапа». В источниках по Go обычно используют последние два варианта, тем не менее я предпочту «словарь». Словарь хорош тем, что обеспечивает доступ к значению по ключу за константное время O(1), независимо от размера коллекции — в отличие от поиска в массиве или срезе.
Как и срезы, словари создаются командой make. Вторым аргументом можно указать емкость коллекции, если она известна заранее. При превышении емкости она будет автоматически увеличена — здесь все так же, как и в случае со срезами.
Доступ к элементу коллекции происходит по ключу, который передается в квадратных скобках, так же как индекс массива или среза: value :. Если ключ не найден, вернется значение по умолчанию для этого типа (zero value).
Возможно, у тебя здесь сразу возник вопрос: а что, если zero value — легитимное значение для элемента нашей коллекции? Как отличить две ситуации: в коллекции есть искомый ключ, и с ним связано значение 0 (или пустая строка), или в коллекции нет этого ключа, и мы получили 0 (или пустую строку) как значение по умолчанию для типа int (или string)?
На самом деле здесь возвращаются два значения: value, , и как раз второе указывает, был ли найден ключ. Если нам нужно только проверить существование ключа, первое значение можно вообще отбросить: _, .
Запись значения происходит похожим образом: result[. При этом значение будет добавлено в коллекцию, если такого ключа в ней еще нет, или заменено, если такой ключ уже существует.
Для удаления ключа используется функция delete(. В отличие от append(, с которой мы познакомились ранее, delete( изменяет существующую коллекцию, а не возвращает новую. Если удаляемый ключ и так отсутствует в коллекции, delete( завершается без ошибок.
Реализация множеств
В некоторых языках программирования ты мог видеть еще такую коллекцию данных, как set (множество). Множество похоже на map в том плане, что это тоже набор уникальных ключей и обеспечивает константную сложность доступа O(1). Отличие состоит в том, что множество содержит только ключи, без значений.
Если тебе понадобится такая структура данных в Go, то идиоматичной реализацией является map с ключами требуемого типа и значениями типа «пустая структура» — struct{. Почему именно пустая структура? Все просто: в Go ее размер составляет ровно ноль байт.
// Создаем set строкmySet := make(map[string]struct{})// Добавляем элементmySet["newElement"] = struct{}{}// Удаляем элементdelete(mySet, "spareElement")// Проверяем наличие элемента_, ok := mySet["checkElement"]if ok { // Найден!}
Пакет strings
Теперь перейдем к парсингу текстовых данных. В этом нам поможет пакет strings из стандартной библиотеки Go (смотри, сколько всего мы уже сделали, и до сих пор обходимся средствами стандартной библиотеки!). Как ты уже догадался по названию, этот пакет объединяет средства работы со строками.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»
