Содержание статьи
В простейшем случае файл содержит одно значение (токен) на строку — так обычно построены вордлисты для брута или фаззинга. В других случаях файл имеет заданную регулярную структуру, например всевозможные вариации 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 (смотри, сколько всего мы уже сделали, и до сих пор обходимся средствами стандартной библиотеки!). Как ты уже догадался по названию, этот пакет объединяет средства работы со строками.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
