Golang прочитать файлы в директории

Обновлено: 06.07.2024

Standard Go Project Layout

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

Если вы пытаетесь изучить Golang или собрать маленький обучающий проект для личного пользования, данный макет будет явным перебором. Стоит начать с чего-нибудь действительно простого (одного файла main.go будет более чем достаточно). Как только ваш проект начнет расти, стоит задуматься о важности содержания кода в структурированном состоянии, чтобы в конечном итоге не получить грязный код с множеством скрытых зависимостей и global state. А как только над проектом начнут работать другие люди - понадобится еще большая структуризация. В этот момент важно определить стандартный путь организации пакетов/библиотек. Если вы разрабатываете проект с открытым исходным кодом или знаете, что вашим кодом будут пользоваться при разработке других проектов, необходимо понимать важность создания личных, внутренних (или internal ) пакетов и кода. Склонируйте репозиторий, пользуйтесь тем, что действительно нужно,и удалите всё остальное! Наличие этого "всего остального" вовсе не означает, что это обязательно использовать. Заметьте, что ни один из этих шаблонов не обязан быть использован в абсолютно каждом проекте. Даже vendor не может быть универсален во всех случаях.

С входом обновления Golang 1.14, Go Modules стали наконец-то доступны для использования. Применяйте Go Modules везде, пока не столкнётесь с особой причиной отказаться от этого, и если такой момент всё же настанет - вам больше не придётся волноваться о значении переменной окружения $GOPATH и месте, где вы размещаете свой проект. Базовый go.mod файл в репозитории показывает, что ваш проект размещён на GitHub, однако он не является обязательным. Путь к модулю может быть любым, при условии, что первый компонент пути должен содержать точку в имени (текущая версия Golang больше не требует этого, но если вы используете достаточно устаревшие версии - не стоит удивляться, что ваши сборки могу перестать работать). Ознакомьтесь с проблемами: 37554 и 32819 если хотите узнать об этом больше.

Этот шаблон организации проекта намеренно делан общим, и не является примером структуры какого-то конкретного пакета Golang.

Этот репозиторий открыт к усилиям сообщества. Создайте заявку о проблеме, если вы нашли новый шаблон или считаете, что один из существующих шаблонов необходимо обновить.

Если вам нужна помощь в наименовании, форматировании или стилизации кода - начните с gofmt и golint . Также обязательно прочтите эти руководства по стилизации кода Golang и рекомендации:

Обратите внимание на Шаблон проекта Golang для получения дополнительной информации.

Еще больше про наименование и организацию пакетов, а так же про структуру кода можно узнать здесь:

Пост о руководствах по пакетноориентированному дизайну и макетам архитектур

Основные приложения проекта.

Имя директорий для каждого приложения должно совпадать с именем исполняемого файла, который вы хотите собрать (например, /cmd/myapp ).

Не стоит располагать в этой директории большие объёмы кода. Если вы предполагает дальнейшее использование кода в других проектах, вам стоит хранить его в директории /pkg в корне проекта. Если же код не должен быть переиспользован где-то еще - ему самое место в директории /internal в корне проекта. Вы будете удивлены, что другие люди могут сделать, поэтому будьте уверены в своих намерениях!

Самой распространнёной практикой является использование маленькой main функции, которая импортирует и вызывает весь необходимый код из директорий /internal и /pkg и никаких других.

Ознакомьтесь с директорией /cmd для примера.

Внутренний код приложения и библиотек. Это код, который не должен быть применен в других приложениях и библиотеках. Стоит отметить, что этот шаблон навязан самим компилятором Golang. Ознакомьтесь с release notes Go 1.4. Также, вы вольны использовать internal директорию на разных уровнях своего проекта.

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

Код библиотек, пригодных для использования в сторонних приложениях. (например, /pkg/mypubliclib ). Другие проекты будут импортировать эти библиотеки, ожидая их автономной работы, поэтому стоит подумать дважды, прежде чем класть сюда какой-нибудь код :-) Заметьте, что использование директории internal - более оптимальный способ не дать импортировать внутренние пакеты, потому что это обеспечит сам Golang. Директория /pkg - всё еще хороший путь дать понять, что код в этой директории могут безопасно использовать другие. Пост I'll take pkg over internal в блоге Трэвиса Джеффери предоставляет хороший обзор директорий pkg и internal и когда есть смысл их использовать.

Существует возможность группировать код на Golang в одном месте, когда ваша корневая директория содержит множество не относящихся к Go компонентов и директорий, что позволит облегчить работу с разными инструментами Go (как упомянуто в этих разговорах: Best Practices for Industrial Programming с GopherCon EU 2018, GopherCon 2018: Kat Zien - How Do You Structure Your Go Apps и GoLab 2018 - Massimiliano Pippi - Project layout patterns in Go).

Ознакомьтесь с директорией /pkg , если хотите увидеть, какие популярные репозитории используют такой шаблон организации проекта. Несмотря на его распространенность, он не был принят всеми, а некоторые в обществе Go и вовсе не рекомендует его использовать.

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

Зависимости приложений, управляемые вручную или с использованием вашей любимой системы управления зависимостями, вроде новых встроенных Go Modules ). Команда go mod vendor создаст для вас директорию /vendor . Заметьте, что вам возможно придётся добавить флаг -mod=vendor к команде go build , если вы используете версию, отличную от Go 1.14, где такой флаг выставлен по-умолчанию.

Не стоит отправлять зависимости вашего приложения в репозиторий, если собираетесь создавать библиотеку.

Спецификации OpenAPI/Swagger, файлы JSON schema, файлы определения протоколов.

Ознакомьтесь с директорией /api для примеров.

Специальные компоненты для веб-приложений: статические веб-ресурсы, серверные шаблоны и одностраничные приложения.

Шаблоны файлов конфигураций и файлы настроек по-умолчанию.

Положите файлы конфигураций confd или consul-template сюда.

Файлы конфигураций для процессов инициализации системы (systemd, upstart, sysv) и менеджеров процессов (runit, supervisord).

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

Ознакомьтесь с директорией /scripts для примеров.

Упаковка и непрерывная интеграция.

Поместите файлы конфигурации и скрипты облака (AMI), контейнера (Docker), пакетов (deb, rpm, pkg) в директорию /build/package .

Поместите ваши файлы конфигурации CI (travis, circle, drone) и скрипты в директорию /build/ci . Отметьте, что некоторые инструменты CI (например, Travis CI) очень требовательны к расположению их конфигурационных файлов. Попробуйте поместить их в директорию /build/ci создав ссылку на них в месте, где их ожидают найти инструменты Go (когда возможно).

Шаблоны и файлы конфигураций систем оркестраций IaaS, PaaS, операционных систем и контейнеров (docker-compose, kubernetes/helm, mesos, terraform, bosh). Отметьте, что в некоторых репозиториях, особенно в приложениях, развернутых с использованием Kubernetes, эта директория называется /deploy .

Дополнительные внешние приложения и данные для тестирования. Вы вольны организовывать структуру директории /test так, как вам угодно. Для больших проектов имеет смысл создавать вложенную директорию с данными для тестов. Например, /test/data или /test/testdata , если вы хотите, чтобы Go игнорировал находящиеся там файлы. Отметьте, что Go будет также игнорировать файлы, путь к которым начинается с "." или "_", что предоставляет вам гибкость в наименовании вашей папки с тестами.

Ознакомьтесь с директорией /test для примеров.

Документы пользователей и дизайна (в дополнение к автоматической документации godoc).

Ознакомьтесь с директорией /docs для примеров.

Инструменты поддержки проекта. Отметьте, что эти инструменты могут импортировать код из директорий /pkg и /internal .

Ознакомьтесь с директорией /tools для примеров.

Примеры ваших приложений и/или библиотек.

Ознакомьтесь с директорией /examples для примеров.

Внешние вспомогательные инструменты, ответвления кода и другие сторонние утилиты (например, Swagger UI).

Другие ресурсы, необходимые для работы (картинки, логотипы и т.д.)

Здесь можно разделить файлы для вебсайта вашего проекта, если вы не используете GitHub pages .

Ознакомьтесь с директорией /website для примеров.

Директории, которые не стоит размещать у себя в проекте

Некоторые проекты на Go имеют директорию src , но это обычно происходит, когда разработкой занялся человек, пришедший из мира Java, где такой подход весьма распространен. Постарайтесь не использовать его в разработке на Golang, если не хотите, чтобы ваш код или проект выглядел, будто написан на Java :-).

Не путайте директорию уровня проекта /src с директорией /src , которую Go использует для своих рабочих пространств, как это описано в How to Write Go Code . Переменная окружения $GOPATH указывает на ваше текущее рабочее пространство (по-умолчанию - $HOME/go на системах под управлением ОС, отличной от Windows). Это рабочее пространство включает высокоуровневые директории /pkg , /bin и /src . Ваш проект в свою очередь находится во вложенной в /src директории, поэтому если вы имеете директорию /src внутри вашего проекта, путь к нему будет выглядеть примерно так: /some/path/to/workspace/src/your_project/src/your_code.go . Отметьте, что в версиях после Go 1.11 возможно хранить проект отдельно от локации, описанной в GOPATH , но это всё еще не значит, что применять этот Java-шаблон - хорошая идея.

GoDoc - Предоставит онлайн версию вашей сгенерированной GoDoc документации


Чтение с диска и запись на диск, а также перемещение по файловой системе — это основной элемент в любом языке. Узнаем, как все это делать в Go с помощью пакета os, который позволяет взаимодействовать с функциональностью операционной системы.

Создание и открытие файлов

Создание файлов происходит с помощью os.Create , а открытие — с помощью os.Open . И там и там принимается путь к файлу и возвращается структура File , а в случае неуспеха — ошибка с nil .

Когда os.Create вызывается в существующем файле, он этот файл обрезает: данные файла стираются. В то же время вызов os.Open в несуществующем файле приводит к ошибке.

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

После взаимодействия с возвращенным файлом закрываем его с помощью File.Close .

Чтение файлов

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

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

Имейте в виду, что os.ReadFile прочитает весь файл и загрузит его данные в память. И чем больше файл, тем больший объем памяти будет потребляться при использовании os.ReadFile .

Эффективный с точки зрения потребления памяти подход связан с пофрагментной обработкой файла, осуществляемой с помощью os.Open .

После открытия файла происходит многократный вызов File.Read до EOF (конца файла).

File.Read принимает байтовый массив b и загружает до len(b) байтов из файла в b . А затем возвращает количество прочитанных байтов bytesRead и ошибку, если что-то пойдет не так. При bytesRead равным 0 нажимаем EOF и заканчиваем обработку файла.

В приведенном выше коде из файла загружается максимум 10 байтов. Они обрабатываются, и этот процесс повторяется до EOF (конца файла).

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

Запись и добавление в файлы

Для записи байтов в файл существует аналогичная os.ReadFile функция os.WriteFile .

Что следует учесть при использовании os.WriteFile :

  • Обязательно преобразуйте данные для записи в []byte , прежде чем передавать их в os.WriteFile .
  • Биты полномочий необходимы для создания файла, если он еще не существует. Но на них заострять внимание не стоит.
  • Если путь к файлу уже существует, os.WriteFile переопределит исходные данные в файле с помощью новых записываемых данных.

os.WriteFile хорош для создания нового файла или его переопределения. Но он не работает, когда нужно сделать добавление к имеющемуся содержимому файла. Для добавления в файл нужно задействовать os.OpenFile .

Согласно документации, os.OpenFile — это более обобщенная версия os.Open и os.Create . И os.Create , и os.Open внутренне вызывают его.

Кроме пути к файлу, os.OpenFile принимает флаги int и perm (биты полномочий) и возвращает структуру File . Для выполнения таких операций, как чтение и запись, в os.OpenFile должна быть указана правильная комбинация флагов .

O_APPEND и O_WRONLY объединяют с побитовым ИЛИ и передают в os.OpenFile для получения структуры File . После этого при вызове File.Write с любыми передаваемыми данными эти данные будут добавлены в конец файла.

Удаление файлов

os.Remove принимает путь к файлу или пустому каталогу и удаляет этот файл/каталог. Если файл не существует, будет возвращена ошибка с nil .

Освоив основы работы с файлами, перейдем теперь к каталогам.

Создание каталогов

Для создания нового каталога используется os.Mkdir . Эта функция принимает имя каталога и биты полномочий, и так создается новый каталог. Если os.Mkdir не создаст каталог, будет возвращена ошибка с nil .

В некоторых ситуациях бывают нужны временные каталоги, которые существуют только во время выполнения программы. Для создания таких каталогов используется os.MkdirTemp .

os.MkdirTemp снабжает создаваемые временные каталоги уникальными именами, даже когда происходят вызовы от нескольких горутин или программ (источник). Закончив работу с временным каталогом, обязательно удалите вместе с его содержимым с помощью os.RemoveAll .

Чтение каталогов и перемещение по ним

Сначала с помощью os.Getwd получим текущий рабочий каталог:

В добавок к изменению рабочего каталога у нас есть возможность получить дочерний каталог. Делается это с помощью os.ReadDir . Эта функция принимает путь к каталогу и возвращает массив структур DirEntry и ошибку c nil в случае неуспеха.

Вот пример использования:

Пройдемся по каталогу

filepath.WalkDir принимает каталог root , из которого мы стартуем, и функцию обратного вызова fn следующего типа:

fn будет вызываться в каждом файле и подкаталоге каталога root . Вот пример подсчета всех файлов в корневом каталоге:

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

Предупреждаю: некоторые библиотеки достаточно очевидны (или были объяснены в предыдущих главах), многие из библиотек, включённых в Go требуют специальных знаний (например: криптография). Объяснение этих технологий выходит за рамки этой книги.

Строки

Go содержит большое количество функций для работы со строками в пакете strings :

Иногда нам понадобится работать с бинарными данными. Чтобы преобразовать строку в набор байт (и наоборот), выполните следующие действия:

Ввод / Вывод

Прежде чем мы перейдем к работе с файлами, нужно узнать про пакет io . Пакет io состоит из нескольких функций, но в основном, это интерфейсы, используемые в других пакетах. Два основных интерфейса — это Reader и Writer . Reader занимается чтением с помощью метода Read . Writer занимается записью с помощью метода Write . Многие функции принимают в качестве аргумента Reader или Writer . Например, пакет io содержит функцию Copy , которая копирует данные из Reader во Writer :

Чтобы прочитать или записать []byte или string , можно использовать структуру Buffer из пакета bytes :

Buffer не требует инициализации и поддерживает интерфейсы Reader и Writer . Вы можете конвертировать его в []byte вызвав buf.Bytes() . Если нужно только читать строки, можно так же использовать функцию strings.NewReader() , которая более эффективна, чем чтение в буфер.

Файлы и папки

Для открытия файла Go использует функцию Open из пакета os . Вот пример того, как прочитать файл и вывести его содержимое в консоль:

Мы используем defer file.Close() сразу после открытия файла, чтобы быть уверенным, что файл будет закрыт после выполнения функции. Чтение файлов является частым действием, так что вот самый короткий способ сделать это:

А вот так мы можем создать файл:

Чтобы получить содержимое каталога, мы используем тот же os.Open() , но передаём ему путь к каталогу вместо имени файла. Затем вызывается функция Readdir :

Иногда мы хотим рекурсивно обойти каталоги (прочитать содержимое текущего и всех вложенных каталогов). Это делается просто с помощью функции Walk , предоставляемой пакетом path/filepath :

Функция, передаваемая вторым аргументом, вызывается для каждого файла и каталога в корневом каталоге (в данном случае).

Ошибки

Контейнеры и сортировки

В дополнение к спискам и картам, Go предоставляет еще несколько видов коллекций, доступных в пакете container . В качестве примера рассмотрим container/list .

Список

Пакет container/list реализует двусвязный список. Структура типа данных связного списка выглядит следующим образом:


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

Пустым значением List (вероятно, опечатка и имелось ввиду x — прим. пер.) является пустой список ( *List создаётся при вызове list.New ). Значения добавляются в список при помощи PushBack . Далее, мы перебираем каждый элемент в списке, получая ссылку на следующий, пока не достигнем nil .

Сортировка

Пакет sort содержит функции для сортировки произвольных данных. Есть несколько предопределённых функций (для срезов, целочисленных значений и чисел с плавающей точкой). Вот пример, как отсортировать ваши данные:

Хэши и криптография

Функция хэширования принимает набор данных и уменьшает его до фиксированного размера. Хэши используются в программировании повсеместно, начиная от поиска данных, заканчивая быстрым детектированием изменений. Хэш-функции в Go подразделяются на две категории: криптографические и некриптографические.

Некриптографические функции можно найти в пакете hash , который включает такие алгоритмы как adler32 , crc32 , crc64 и fnv . Вот пример использования crc32 :

Объект crc32 реализует интерфейс Writer , так что мы можем просто записать в него набор байт, как и в любой другой Writer . После записи мы вызываем Sum32() , который вернёт uint32 . Обычным применением crc32 является сравнение двух файлов. Если значение Sum32() для обоих файлов одинаковы, то, весьма вероятно (не со стопроцентной гарантией), содержимое этих файлов идентично. Если же значения отличаются, значит файлы, безусловно, разные:

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

Одним из криптографических хэш-алгоритмов является SHA-1. Вот как можно его использовать:

Этот пример очень похож на пример использования crc32 , потому что оба они реализуют интерфейс hash.Hash . Основное отличие в том, что в то время как crc32 вычисляет 32-битный хэш, sha1 вычисляет 160-битный хэш. В Go нет встроенного типа для хранения 160-битного числа, поэтому мы используем вместо него срез размером 20 байт.

Серверы

На Go очень просто создавать сетевые серверы. Сначала давайте взглянем, как создать TCP сервер:

HandleFunc обрабатывает URL-маршрут ( /hello ) с помощью указанной функции. Мы так же можем обрабатывать статические файлы при помощи FileServer :

Пакеты net/rpc (remote procedure call — удаленный вызов процедур) и net/rpc/jsonrpc обеспечивают простоту вызова методов по сети (а не только из программы, в которой они используются).

run и myfile.go являются аргументами. Мы так же можем передать команде флаги:

Пакет flag позволяет анализировать аргументы и флаги, переданные нашей программе. Вот пример программы, которая генерирует число от 0 до 6. Но мы можем изменить максимальное значение, передав программе флаг -max=100 .

Любые дополнительные не-флаговые аргументы могут быть получены с помощью flag.Args() , которая вернет []string .

Синхронизация примитивов

Мьютексы

Мьютекс (или взаимная блокировка) единовременно блокирует часть кода в одном потоке, а так же используется для защиты общих ресурсов из не-атомарных операций. Вот пример использования мьютекса:

Когда мьютекс ( m ) заблокирован из одного процесса, любые попытки повторно блокировать его из других процессов приведут к блокировке самих процессов до тех пор, пока мьютекс не будет разблокирован. Следует проявлять большую осторожность при использовании мьютексов или примитивов синхронизации из пакета sync/atomic .

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

Я не могу найти file.ReadLine функцию в Go. Я могу понять, как быстро написать один, но мне просто интересно, что я здесь пропускаю. Как читать файл построчно?

Начиная с Go1.1, bufio.Scanner - лучший способ сделать это.

ПРИМЕЧАНИЕ . Принятый ответ был правильным в ранних версиях Go. Смотрите ответ с наибольшим количеством голосов содержит более свежий идиоматический способ достижения этого.

В пакете есть функция ReadLine bufio .

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

bufio.ReadString('\n') не полностью эквивалентен, ReadLine потому что ReadString не может обработать случай, когда последняя строка файла не заканчивается символом новой строки.

Из документов: «ReadLine - это низкоуровневый примитив для чтения строк. Большинству абонентов следует использовать ReadBytes ('\ n') или ReadString ('\ n') или использовать сканер." @mdwhatcott, почему так важно, что это "низкоуровневый примитив для чтения строк"? Как это приводит к выводу, что «Большинство вызывающих абонентов должны использовать вместо них ReadBytes ('\ n') или ReadString ('\ n') или использовать сканер."? @CharlieParker - Не уверен, просто цитируя документы, чтобы добавить контекст. Из тех же документов . "Если ReadString обнаруживает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF)." Таким образом, вы можете просто проверить на наличие ошибки io.EOF и знать, что все сделано. Обратите внимание, что чтение или запись может завершиться ошибкой из-за прерванного системного вызова, что приводит к тому, что число читаемых или записываемых байтов меньше ожидаемого.

В Go 1.1 и новее самый простой способ сделать это с помощью bufio.Scanner . Вот простой пример, который читает строки из файла:

Это самый чистый способ читать Reader построчно.

Есть одно предупреждение: сканер плохо справляется со строками длиннее 65536 символов. Если это проблема для вас, тогда вам, вероятно, стоит свернуть свою собственную Reader.Read() .

И поскольку ОП попросил просканировать файл, сначала было бы тривиально, file, _ := os.Open("/path/to/file.csv") а затем просканировал дескриптор файла: scanner := bufio.NewScanner(file) Проблема в том, что Scanner.Scan () ограничен размером буфера в 4096 байт на строку. Вы получите bufio.ErrTooLong ошибку, bufio.Scanner: token too long если строка слишком длинная. В этом случае вам придется использовать bufio.ReaderLine () или ReadString (). Просто мои 0,02 доллара - это самый правильный ответ на странице :)