Golang удалить файл если существует
I'm new to Go and can't seem to find a way to delete all the contents of a directory when I don't know the contents.
but get remove *: no such file or directory or invalid argument .
And of course if you do:
it deletes the tmp directory as well. Which is not what I want.
@0x434D53: Shell commands are OS and shell dependent. Use Go package os OS and shell independent functions for portability. For example, I tested my Go solution on Linux and Windows.
Ofc they are. Was just an additional suggestion that might fit his usecase. Had a reason i did not write it as an answer.
5 Answers 5
It most probably existed as he talked about removeall , so it might do something unexpected, but I'd like to know what as I use this method pretty often
@Lomithrani RemoveAll says that it exits on the first encounter of an error. Individually removing each file would allow you to remove more in a case where multiple errors would be expected. That's all I could come up with after reading the documentation.
oh hang on, I think we were trying to delete the contents of a directory but not the directory itself. As I said at the bottom of my question removeAll removes the parent directory as well. Making the directory again would work but would need to match the current permissions set on it.
Write a simple RemoveContents function. For example,
thanks for this, I have actually gone with something similar but using ioutil.ReadDir to save opening the dir. Thanks
@mattl: That's not good. For package os , "Readdirnames reads and returns a slice of names from the directory." For package ioutil , "ReadDir reads the directory named by dirname and returns a list of sorted directory entries." Switching from os to ioutil , you added an unnecessary sort of the name list: func ReadDir : sort.Sort(byName(list)) .
Just use ioutil.ReadDir to get a slice of os.FileInfo types, then iterate through and remove each child item using os.RemoveAll .
That way, you are removing only all the child items and not the parent /tmp folder itself.
Deleting files in Go is that simple. File delete, file create, file read, and file writes. Almost all the file operations are done through the os package. So if you want to manage files in Golang, you need to use Golang inbuilt os package.
How To Remove File In Golang
To remove a file in Golang, use the os.Remove() function. You need to provide a filepath to that file, and the function removes that file. Golang Remove() removes the named file or (empty) directory.
If there is an error, that will be of type *PathError. PathError means the program could not find the provided file in a particular path; that is why it throws an error.
func Remove()
The os.Remove() is an inbuilt function that removes a file in Go.
See the following syntax.
It accepts one argument and that file filename(filepath). Next, we specify a target directory. You will want to adjust this to point to a location on your computer. Use a path syntax for your target platform. The Remove() function removes the named file or (empty) directory.
The sample snippet to remove a file in go is the following.
Now let’s say I have one file called todo.tmpl, and I want to remove it, then my code will follow.
We need to pass the filepath in the function parameter and done. It will remove the file.
Run the program and see the output.
Now, let’s rerun the file, and we will get the error because the file is already removed, and this time, it won’t be there to delete, so that it will throw an error.
Before deleting many files, it sometimes helps to have a confirmation or some logic test.
It can be hard to recover from a mass deletion made in error.
We can directly invoke os.Remove() with a path argument to delete a file. And in a loop, we can delete the entire contents of a directory.
func RemoveAll()
Golang RemoveAll() function removes the path and any children it contains.
The RemoveAll() method removes everything it can but returns the first error it encounters. If the path does not exist, RemoveAll returns nil (no error). If there is an error, it will be of type *PathError.
See the following syntax of the function.
Conclusion
If we want to remove just one file, use Remove(), and if we’re going to remove a complete folder and its files, then use RemoveAll() function.
That’s it for this tutorial.
See also
Krunal Lathiya is an Information Technology Engineer. By profession, he is a web developer with knowledge of multiple back-end platforms (e.g., PHP, Node.js, Python) and frontend JavaScript frameworks (e.g., Angular, React, and Vue).
Чтение с диска и запись на диск, а также перемещение по файловой системе — это основной элемент в любом языке. Узнаем, как все это делать в 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 . Вот пример подсчета всех файлов в корневом каталоге:
В path/filepath есть еще одна функция filepath.Walk с поведением, аналогичным filepath.WalkDir . Однако в документации сказано, что filepath.Walk менее эффективна, чем filepath.WalkDir . Поэтому лучше использовать filepath.WalkDir .
Надеюсь, эта статья помогла вам сделать еще один шаг в изучении Go в том, что касается основ работы с файлами и каталогами. Закрепить материал рекомендую реализацией вашей собственной версии filepath.WalkDir .
У меня на работе периодически возникает необходимость перемещать файлы, выгруженные из одной программы или комплекса, во входящие каталоги другой системы либо на сетевой диск, чтобы их забрал пользователь. Обычно в простейшем случае для этого используются пакетные файлы или shell-скрипты в комбинации с cron-ом/планировщиком.
Попробуем для этих целей написать программу на Go.
Какие моменты мы затронем в этой статье.
- Работа с YAML, JSON.
- Каналы, мьютексы.
- Горутины.
- Создание веб-сервера.
- Копирование и перемещение файлов.
В заголовке написано “тоссер”, но наша программа будет обладать лишь способностью перекладывать файлы, что является одной из функций тоссеров. Подробнее про настоящие эхопроцессоры можно прочитать тут.
Напомню, что я не профессиональный программист на Go, а только учусь. Если какие-то реализации кода, увиденные в статье вам покажутся не go-way, жду пулл-реквестов, респонсов и т.п. :)
Сформируем требования к программе.
- Хранение настроек в файле.
- Перемещение файлов между локальными каталогами и/или сетевыми дисками.
- Одновременная обработка более одной пары каталогов источник-назначение, то есть работа в несколько потоков.
- Наличие в настройках вариантов выбора действия, если файл в конечной папке уже существует. Например, перезапись и пропуск.
- Возможность задать для одной сканируемой папки несколько правил обработки (какие файлы искать и в куда их перемещать).
- Возможность задавать списки исключений для файлов.
- Отображение статистики работы в веб-браузере.
Для простоты отбросим необходимость обрабатывать каталоги рекурсивно.
Примеры кода можно скачать на 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.
Кстати, программа пока ничего не знает, что ей делать, если файл в конечном каталоге уже существует.
В принципе наша программа уже вполне рабочая за исключением нескольких моментов.
- Если копирование/перемещение файла будет выполняться долго, то процедура processItems попытается повторно закинуть файл в канал копирования. И если свободная горутина processItem захватит такой файл, то очень вероятно произойдет ошибка копирования, так как он будет занят другой горутиной, которая начала первой его обрабатывать. Нам необходимо сделать так, чтобы файл не ставился в обработку дважды.
- Необходимо сделать так, чтобы отсутствующие каталоги создавались автоматически для каталога-назначения и если указано в конфигурационном файле (create_src) для каталога-источника.
- Сейчас программа ничего не знает, что ей делать, если файл в целевом каталоге уже существует.
- Надо добавить обработку масок исключений, заданных в настройках.
Итак, сосредоточимся на данных функциях.
Исключение обработки одного файла дважды
Для того чтобы файлы не попадали в обработку дважды, сделаем “кэш” обрабатываемых файлов и перед отправкой файла в обработку, проверим нет ли его уже в кэше.
Создаем файл processing_cache.go
Чтобы две горутины не пытались вставить один и тот же файл одновременно в кеш мы используем RWMutex.
Добавляем создание кэша в объявление.
И проверки на наличие файла в кэше.
Вставим паузу в процедуру перемещения, чтобы проверить работу кэша.
Компилируем, кидаем файл во входящий каталог и запускаем программу.
Также сделаем так, чтобы один и тот же каталог не ставился на сканирование дважды. Это необходимо, чтобы исключить ситуацию, когда одна горутина сканирования еще обрабатывает список файлов (например, их много или чтение списка замедлено), а вторая получает тот же самый список и запускает обработку файлов, до которых первая горутина еще не дошла. Также это поможет исключить ненужные операции (получение списка файлов, его обработка, проверка на кэш и т.п.).
Вставляем паузу для теста в processItems.
Компилируем и проверяем.
Убираем из кода тестовые паузы.
Создание каталогов
Для создания каталогов воспользуемся функцией os.MkdirAll
Компилируем. Удаляем каталоги. Запускаем. Каталог-источник должен создаться автоматически. Если в него после этого кинуть файл, то он должен переместиться в каталог-назначения, который тоже создастся сам.
Проверка на существование файла
Проверим, что файл существует функцией os.Stat, а затем удалим его, если задано ifexists: replace.
Список исключений
Всего у нас предусмотрено три списка исключений.
- Глобальный - действует на все файлы.
- Локальный для группы - действует только на файлы внутри папок группы
- Локальный для правила - действует только на файлы, которые попали под маски определенного правила.
Проверки осуществляются с помощью уже знакомой функции filepath.Match.
Делаем функцию для проверки исключений.
Правда, для проверки глобального списка исключений, нам потребовался доступ к переменной cfg и мы её сделали глобальной.
В присваивании reloadConfig мы убрали двоеточие, чтобы не создавалась локальная переменная, а выражение присваивалось глобальной.
Исходник можно посмотреть тут.
В этой части мы реализуем подсчет статистики по переданным файлам для каждого каталога и отображение этой статистики в веб-браузере.
Разделим эту часть на два блока.
Подсчет статистики
Статистика будет собираться по каждому каталогу-источнику отдельно. Нас интересует количество переданных файлов из каталога-источника и их объем. Если файл передавался в два разных каталога-назначения, то учитываем такой файл один раз.
Для того чтобы подсчитывать статистику нам понадобиться немного модифицировать имеющийся код. В частности, нам надо добавить проверку на то, успешно ли скопровался/переместился файл. Если да, тогда сохраняем по этому файлу статистику.
Выше мы добавили две переменные для проверки успешности обработки файла и проверки на перемещение, чтобы зря не прогонять уже перемещённый файл по оставшимся правилам, которые ничего не смогут с ним сделать. Также мы добавили канал, куда будем отправлять информацию по успешно обработанному файлу.
Создаем канал, про который написано выше, и переменную tosserstat, которая имеет тип *TosserStat.
У структуры TosserStat есть методы load, save, update и т. п. Посмотреть полный текст stat.go можно тут. Вся статистика сохраняется в формате JSON в папке tmp/stat.json. Папка задаётся в константе statfile. Сохранение происходит каждые 10 секунд, обновление - как только что-нибудь будет передано в канал savestatchan.
Добавляем запуск цикла обновления статистики.
Компилируем, запускаем. После передачи файлов видим, что в папке tmp создался файл stat.go.
It is no doubt that files are some of the important tools for programmers. For example, we can use files to read data or even store logs from our programs. This means that we need to perform operations such as creating and deleting files and directories.
This article will learn how to delete or remove files from the system using the Go programming language.
Golang OS Package
The OS package provides the functionality to remove a file in go. Hence we need to import it as shown in the snippet below:
Once imported, we can use it.
Delete a Single File
To delete a single file, we use the Remove() method. The syntax is as shown:
For example, to remove a file from the current directory, we can do:
packagemain
import (
"log"
"os"
)
funcmain () <
err := os . Remove ( "hello.txt" )
if err != nil <
log . Fatal ( err )
>
>
The above example deletes the file “hello.txt” from the current working directory.
If you want to remove a file outside of your current working directory, you can use the absolute path as shown in the example below:
package main
import (
"log"
"os"
"path/filepath"
)
funcmain () <
path := filepath . Join ( "dir1" , "dir2" , "filename.txt" )
err := os . Remove ( path )
if err != nil <
log . Fatal ( err )
>
>
In the example above, we use the filepath.Join() method to create an absolute path to the file.
We then pass the filepath to the Remove() method.
Check our tutorial on Golang or join the path to learn more.
Delete Directory and Subdirectories
The os package also provides us with the RemoveAll() method. This takes a path to a directory and removes all the files and subdirectories inside it.
A sample code is as shown:
err := os . RemoveAll ( "path_to_directory" )
log . Fatal ( err )
fmt . Println ( "Directory removed!" )
The example above removes the directory in the specified path.
Conclusion
In this guide, you learned how to delete files and directories using the os package from the Go standard library.
About the author
John Otieno
My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list
Читайте также: