Чтение, запись и удаление файлов

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

Давайте предположим, что в нашей файловой системе есть файл example.txt, который содержит следующее:

- Привет! Ты из Китая, верно?
– Да, верно. Привет.
- Я слышал, что Китай был первой страной, которая приняла идеи коммунизма.
- Да, это правда. Мы пытаемся построить социалистическое общество с китайской спецификой.
- А что вы думаете об идеологии коммунизма?
- Я думаю, что коммунизм может быть хорошим идеалом, если будет правильно реализован. Он должен быть основан на равном распределении ресурсов и справедливости для всех людей.
- А что вы думаете о перестройке в России и распаде Советского Союза?
- Я думаю, что это был сложный период в истории России и СССР. Я уважаю решение народа России о переходе к рыночной экономике и демократии. Но я также считаю, что коммунизм может быть реализован лишь в том случае, если есть поддержка и понимание со стороны жителей страны.

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

# В данном случае мы открываем наш файл по пути '/some/path/example.txt' с правами на чтение
# результатом открытия файла будет обхект класса File содержащий значение файла, а также метаинформацию
file = File.open("/some/path/example.txt", "r")

Можно выделить 3 основных права на файл:

  1. r - read/чтение

  2. a - append/добавление (мы можем добавить в файл значение, но не можем изменить уже существующее)

  3. w - write/запись

Кроме того, права указаны в порядке суммирования, что означает, что каждый пункт будет помимо своих собственных прав иметь еще и права на из пункта выше.

После того как мы открыли файл нам доступны следующие методы:

  • read - перенос значения в строкове представление. При этом наши переносы строки, пробелы, табуляции сохраняются

  • readlines - перенос значения в массив строк, где каждая новая строка будет новым значением в массиве

  • each_line - возвращает объект Enumerator. Если открыть в нем блок кода, то можно проитерировать каждую строчку файла

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

  • seek - перевод курсора к указанной позиции в файле. Принимает первым аргументом размер отступа, а вторым от чего именно будет отступ:

    • :END - с конца

    • :SET - с начала

    • :CUR - от текущей позиции

  • size - размер файла в байтах

  • birthtime - дата создания объекта

  • atime - дата последнего доступа к файлу

Важно понимать отличие между readlines и each_line с readline. reeadlines читает содержимое файла целиком и целиком помещает его в массив. Это может быть эффективно при работе с небольшими файлами, но если файл весит 1 ГБ, то весь 1ГБ будет помещен в память компьютера, а как мы знаем размеры памяти куда более ограничены по сравнению с постоянной памятью по типу SSD или HHD. each_line и readline отличается своей возможностью лениво читать файл. Если текущая итерация обрабатвает 1 строчку файла, то остальные строчки не будут помещены в память. Когда итерация начнет обрабатывать 2 строчку, то данные из первой строки очистятся из памяти, а данные из 3 и последующей не будут в нее загружены.

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

# выглядит точно так же как и обычное открытие файла, только файл помещается не в объект, а в параметр блока кода
File.open("/some/path/example.txt", "r") do |file|
  # лениво итерируемся по каждой строчке файла, экономя память
  file.each_line do |line|
    puts line
  end
end

Изменение файлов

Давайте сразу на примере посмотрим как мы можем записывать в файл:

# Заметьте, для записи файла используется уже другие права на работу с файлом
# Если файла на момент работы с ним не существует, то он будет создан
File.open("/some/path/another_example.txt", "w") do |file|
  # Записываем в файл строку
  file.write("some data")
end

Обратите внимание, в примере выше запись в файл всегда будет перезаписывать уже существующие данные. Для того, чтобы нам вместо перезаписи дополнять данными пользуются флагом a при записи

# Заметьте, для записи файла используется уже другие права на работу с файлом
# Если файла на момент работы с ним не существует, то он будет создан
File.open("/some/path/another_example.txt", "w") do |file|
  # Записываем в файл строку, старые данные остаются неизменными 
  file.write("some data", mode: "a")
end

Такой же результат мы получим, если откроем файл с правами a

File.open("/some/path/another_example.txt", "a") do |file|
  # Записываем в файл строку, старые данные остаются неизменными 
  file.write("some data")
end

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

  • File.rename - переименновать файл

  • File.size - получить размер файла в байтах

  • File.exists? - существует ли файл

  • File.directory? - является ли файл директорией

  • File.file? - является ли файл файлом

  • File.foreach - такой же функционал как и у метода объекта .each_line

  • File.read - такой же функционал как и у метода объекта .read

  • File.readlines - такой же функционал как и у метода объекта .readlines

Также, методы класса самотоятельно открывают и закрывают файл, соответсвенно, нам об этом заботиться не нужно.

Работа с директорией

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

  • Dir.mkdir - создание новой директории. Внимание этот метод не создает вложнные каталоги. Если мы попытаемся создат ькаталог в несуществующем каталоге, то получим ошибку.

  • Dir.entries - возвращает массив названий файлов и каталогов в указаной директории. Учтите6 что это именно массив названий файлов, а не объектов File.

  • Dir.glob - Возвращает список файлов и директорий по паттерн матчингу. * обозначает любое количество символов, включая ноль символов. ** обозначает любое количество символов, включая вложенные каталоги. Кроме символа * также используются символы ? (заменяет один любой символ) и [] (определяет диапазон возможных символов). Например:

    • *.txt - соответствует всем файлам с расширением .txt в текущей директории.

    • docs/*.txt - соответствует всем файлам с расширением .txt в папке docs, находящейся в текущей директории.

    • **/*.txt - соответствует всем файлам с расширением .txt в любой вложенной директории, начиная с текущей.

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

# Получение списка файлов в текущем каталоге
# Внимание, file_name это только название файла без полного пути
Dir.entries(".") do |file_name|
  puts file_name
end
# Получение списка файлов в текущем каталоге и подкаталогах с расширением .rb
# Внимание, file_name это только название файла без полного пути
Dir.glob("**/*.rb") do |file_name|
  puts file_name
end

Задание file_tree

Напишите метод file_hierachy(path) принимающий путь до директории. Выведите в консоль файлы в древовидной форме. Как это должно выглядеть в консоли:

file_hierachy('/home/ruby_course/Documents')

# /home/ruby_course/Documents
# /home/ruby_course/Documents/Projects
# /home/ruby_course/Documents/Projects/course
# /home/ruby_course/Documents/Projects/course/file1.txt
# /home/ruby_course/Documents/Projects/course/file2.txt
# /home/ruby_course/Documents/Projects/course/file3.txt
# /home/ruby_course/Documents/Projects/photos
# /home/ruby_course/Documents/Projects/photos/photo.jpg
# /home/ruby_course/Documents/Downloads/config.json
# /home/ruby_course/Documents/Downloads/archive.tar
# ...

Задание folder_size

Напишите метод calculate_folder_size(path), который принимает путь до директории и возвращает размер папки в гигабайтах, округленный до 2 символов после запятой

Рекоммендации:

  • Программы должна отлавливать все возможные ошибки (например, если передан несуществующий путь)

Last updated