Golang удалить файл если существует

Обновлено: 06.07.2024

I have made a command line application where I am zipping up folders and sharing on my local server for others to download. What I want to do is delete my copy of the zipped folder as soon as I close the server. This is my code:

When I hit ctrl-c, the program exits and main function closes and as a result,shouldn't os.Remove(xyz) get executed? A tour of go says, defer executes the expression when the function returns. Here, I don't feel main gets the oppurtunity to return anything at all.

What is a workaround to achieve what I am trying to do? I have some solutions in my head like wait for a keypress etc. but I want this program to be super simple,so is there a way to delete the file as soon as the server closes/program exits without requiring any further input from me?

1,757 2 2 gold badges 11 11 silver badges 32 32 bronze badges The main function is interrupted so you need to install a signal handler and call Remove inside of that function. Let me look into what a signal handler is. Thanks for responding. Cool, I guess I should have mentioned that a channel read is blocking. @Krash you may want to consider answering your own question for future reference to others.

1 Answer 1

This has already been answered in the comments, but I'll document it here for completeness.

defer works only when the program and code you're using it in runs through its course normally. Stopping a program with with a command or killing it, on the other hand, sends a signal to the program and then terminates it abnormally, which does not allow the program to run all the defer statements cleanly.

If you call this from main , it will keep a goroutine running for the life of your program listening to the OS signals. And the cleanupAllTheThings() function needs to be written to run as fast as possible without blocking to be effective - you never know when the OS is going to terminate you with prejudice.

Also, this will not protect you from someone pulling out the plug or a kernal panic - so it usually makes sense to have some kind cleanup of the old program state on startup or in a separate cleanup script.

Так просто удалить файлы в Go. Удаление файла, создание файла, чтение файла и запись файла почти все операции с файлами выполняются с помощью пакета os . Поэтому, если вы хотите управлять файлами в Golang, вам необходимо использовать встроенный в Golang пакет os .

Как удалить файл в Golang

Чтобы удалить файл в Golang, используйте функцию os.Remove (). Вам необходимо указать путь к этому файлу, и функция удалит этот файл. Golang Remove () удаляет указанный файл или (пустой) каталог.

Если есть ошибка, она будет иметь тип * PathError . PathError означает, что программе не удалось найти указанный файл по определенному пути; поэтому он выдает ошибку.

func Remove ()

См. следующий синтаксис.

Он принимает один аргумент и это имя файла (путь к файлу). Указываем целевой каталог. Вы захотите настроить это так, чтобы оно указывало на место на вашем компьютере. Используйте синтаксис пути для вашей целевой платформы. Функция Remove () удаляет указанный файл или (пустой) каталог.

Пример фрагмента для удаления файла в go следующий.

Теперь предположим, что у меня есть один файл с именем todo .tmpl, , и я хочу его удалить, затем мой код будет следующим.

Нам нужно передать путь к файлу в параметр функции и готово. Он удалит файл.

Запустите программу и посмотрите результат.

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

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

Мы можем напрямую вызвать os.Remove () с аргументом пути для удаления файла. И в цикле мы можем удалить все содержимое каталога.

func RemoveAll ()

Golang RemoveAll () функция удаляет путь и все дочерние элементы, которые он содержит.

Метод RemoveAll () удаляет все, что может, но возвращает первую обнаруженную ошибку. Если путь не существует, RemoveAll возвращает ноль (без ошибки). Если есть ошибка, она будет иметь тип * PathError.

См. Следующий синтаксис функции.

Заключение

Если мы хотим удалить только один файл, используйте Remove (), и если мы собираемся удалить всю папку и ее файлы, а затем использовать функцию RemoveAll ()


Чтение с диска и запись на диск, а также перемещение по файловой системе — это основной элемент в любом языке. Узнаем, как все это делать в 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 . Вот пример подсчета всех файлов в корневом каталоге:

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

Попробуем для этих целей написать программу на Go.

Какие моменты мы затронем в этой статье.

  1. Работа с YAML, JSON.
  2. Каналы, мьютексы.
  3. Горутины.
  4. Создание веб-сервера.
  5. Копирование и перемещение файлов.

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

Напомню, что я не профессиональный программист на Go, а только учусь. Если какие-то реализации кода, увиденные в статье вам покажутся не go-way, жду пулл-реквестов, респонсов и т.п. :)

Сформируем требования к программе.

  1. Хранение настроек в файле.
  2. Перемещение файлов между локальными каталогами и/или сетевыми дисками.
  3. Одновременная обработка более одной пары каталогов источник-назначение, то есть работа в несколько потоков.
  4. Наличие в настройках вариантов выбора действия, если файл в конечной папке уже существует. Например, перезапись и пропуск.
  5. Возможность задать для одной сканируемой папки несколько правил обработки (какие файлы искать и в куда их перемещать).
  6. Возможность задавать списки исключений для файлов.
  7. Отображение статистики работы в веб-браузере.

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

Примеры кода можно скачать на github.

После клонирования репозитория надо переименовать файл gotosser.yaml.example в gotosser.yaml и настроить в нем каталог-источник и каталог-назначения. Ниже будет представлено содержимое этого файла.

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

Для начала создадим конфигурационный файл в формате YAML.
Хотя он и не поддерживается “из коробки”, но он более удобен для чтения и правки человеком, чем JSON.

Содержимое файла gotosser.yaml, который будем использовать (скачайте его себе или создайте такой же):

Пример файла конфигурации с комментариями можно посмотреть здесь.

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

Итак, создаем файл gotosser.go.

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

Процедура scanLoop пока просто печатает список каталогов, которые необходимо сканировать.

Для чтения настроек будем использовать пакет “gopkg.in/yaml.v2”.

Создаем файл config.go в который поместим логику работы с конфигурационным файлом.

Сначала мы описываем структуру Config, которая будет заполняться в результате выполнения функции yaml.Unmarshal. Обратите внимание в структуре мы задали названия полей с большой буквы, а рядом оставили “подсказку” для yaml.Unmarshal, как на самом деле называются поля внутри файла.

Выкачиваем необходимые пакеты и компилируем программу.

Запускаем и видим примерно такой результат:

Исходник к первой части можно посмотреть тут.

Теперь добавим процедуру сканирования папки.

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

Добавляем запуск processScanGroup в scanLoop.

Процедуру processScanGroup мы запускаем в отдельном потоке.
Теоретически в конфигурационном файле можно указать 1000 каталогов-источников, тогда scanLoop попытается запустить 1000 копий processScanGroup.

Нам необходимо ограничить максимальное число запускаемых процедур processScanGroup, работающих одновременно.
Это можно сделать двумя способами.

Во-первых, мы можем создать канал, для общения между процедурами scanLoop и processScanGroup. И перед запуском цикла в scanLoop запустить нужное конечное число процедур processScanGroup, которые будут считывать данные из этого канала.

Во-вторых, можно создать буферизированный канал типа struct<>определенного размера. И перед запуском go processScanGroup отправлять элемент в этот канал. Если окажется так, что у нас уже запущено число горутин processScanGroup равное объему такого канала, то очередная операция отправки в него элемента заблокируется.

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

Закинем несколько файлов в каталог src1 и запустим программу.

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

Создаём процедуру processItem, которая будет общаться с процедурой processItems через канал processingchan.

Процедура processItem читает из канала элементы и проверяет их на совпадение с масками правил. Чтобы правила обрабатывались не в случайном порядке (так как словарь — это неупорядоченная последовательность), а в соответствии с их номерами, мы делаем выборку правил по отсортированным ключам типа int.

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

Так как в конфигурационном файле для каталога-назначения также полезно иметь возможность задать маску даты(c:\out\%Y%m%d), мы вынесли этот блок в отдельную функцию.

Функции для перемещения и копирования файлов:

Вносим изменения в processItems и processScangroup.

Полный текст программы можно найти в тут.

Компилируем, кладем в папку src1 несколько файлов, создаем папку dst1_1 (так как наша программа пока не умеет создавать недостающие каталоги) и запускаем программу.
В результате файлы должны быть перемещены из src1 в dst1_1.
Кстати, программа пока ничего не знает, что ей делать, если файл в конечном каталоге уже существует.

В принципе наша программа уже вполне рабочая за исключением нескольких моментов.

  1. Если копирование/перемещение файла будет выполняться долго, то процедура processItems попытается повторно закинуть файл в канал копирования. И если свободная горутина processItem захватит такой файл, то очень вероятно произойдет ошибка копирования, так как он будет занят другой горутиной, которая начала первой его обрабатывать. Нам необходимо сделать так, чтобы файл не ставился в обработку дважды.
  2. Необходимо сделать так, чтобы отсутствующие каталоги создавались автоматически для каталога-назначения и если указано в конфигурационном файле (create_src) для каталога-источника.
  3. Сейчас программа ничего не знает, что ей делать, если файл в целевом каталоге уже существует.
  4. Надо добавить обработку масок исключений, заданных в настройках.

Итак, сосредоточимся на данных функциях.

Исключение обработки одного файла дважды

Для того чтобы файлы не попадали в обработку дважды, сделаем “кэш” обрабатываемых файлов и перед отправкой файла в обработку, проверим нет ли его уже в кэше.

Создаем файл processing_cache.go

Чтобы две горутины не пытались вставить один и тот же файл одновременно в кеш мы используем RWMutex.

Добавляем создание кэша в объявление.

И проверки на наличие файла в кэше.

Вставим паузу в процедуру перемещения, чтобы проверить работу кэша.

Компилируем, кидаем файл во входящий каталог и запускаем программу.


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

Вставляем паузу для теста в processItems.

Компилируем и проверяем.


Убираем из кода тестовые паузы.

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

Для создания каталогов воспользуемся функцией os.MkdirAll

Компилируем. Удаляем каталоги. Запускаем. Каталог-источник должен создаться автоматически. Если в него после этого кинуть файл, то он должен переместиться в каталог-назначения, который тоже создастся сам.

Проверка на существование файла

Проверим, что файл существует функцией os.Stat, а затем удалим его, если задано ifexists: replace.

Список исключений

Всего у нас предусмотрено три списка исключений.

  1. Глобальный - действует на все файлы.
  2. Локальный для группы - действует только на файлы внутри папок группы
  3. Локальный для правила - действует только на файлы, которые попали под маски определенного правила.

Проверки осуществляются с помощью уже знакомой функции filepath.Match.

Делаем функцию для проверки исключений.

Правда, для проверки глобального списка исключений, нам потребовался доступ к переменной cfg и мы её сделали глобальной.

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

Исходник можно посмотреть тут.

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

Разделим эту часть на два блока.

Подсчет статистики

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

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

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

Создаем канал, про который написано выше, и переменную tosserstat, которая имеет тип *TosserStat.

Читайте также: