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

В этом уроке вы научитесь работать с файлами в Ruby. Мы будем открывать файлы, читать их содержимое и работать с ним в удобной форме.

Мы узнаем о классе File и о том, как работают его методы new, dirname, exist? и методы его экземпляров read, readlines и close.

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

План урока

  1. Файлы, что это такое и как программы с ними работают
  2. Чтение данных из файла в Ruby и не только
  3. Зачем нужно закрывать файлы и как это делать

Что такое файлы?

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

Как-то так были придуманы файлы.

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

Файлы есть как у вас на домашнем компьютере — ваши документы и фотографии или системные файлы Windows или Mac OS X, так и на любом сервере, к которому вы на самом деле обращаетесь, когда вводите в браузере адрес любого сайта. То, что вы видите как сайт — это тоже файл (чаще всего несколько файлов), подготовленных определённым образом.

Именно поэтому так важно и удобно уметь работать с файлами в ваших программах.

Как программы работают с файлами

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

Файлы — они как книги, только файлы

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

Полка с книгами

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

Стол с открытой книгой

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

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

Давайте напишем простенькую программку, решающую вот такую задачу:

Вывести на экран произвольный афоризм из файла со списком афоризмов.

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

Как обычно, файлы мы храним в нашей папке урока: с:\rubytut\lesson13.

Однако, для файлов с данными, не являющимися текстом программ, удобно всегда создавать вложенные подпапки. Наш файл с цитатами quotes.txt мы положим в подпапку data в нашей рабочей директории. Сам файл будет содержать вот это:

Учиться, учиться и еще раз учиться... программированию! // В. И. Ленин
Слышь, пацан, либы есть? // Ванек со второго подъезда
Я программирую, значит я существую // Фрэнсис Бэкон
Кто не умеет программировать, тот лох :) // Билл Гейтс
Тяжело в учении — легко в программировании // Народная мудрость
Программировали, перепрограммировали, да не запрограммировали // Скороговорка

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

Теперь создадим нашу программу open_file.rb в рабочей папке урока с:\rubytut\lesson13.

В этой программе мы будем открывать файл data/quotes.txt, брать из него произвольную строчку и выводить её на экран.

Для пользователей Windows: убедитесь, что вы сохранили файл quotes.txt в кодировке UTF-8 (для этого тоже можно использовать редактор Sublime), как это делать мы обсуждали во втором уроке.

Открытие файла

Для работы с файлами в Ruby есть специальный встроенный класс File, который позволяет в программе создавать объекты для работы с файлами.

Любой экземпляр класса начинается с конструктора. Также и здесь, чтобы создать новый файловый объект, нам надо у класса File вызвать метод new, который вернёт нам экземпляр класса File.

Методу new нужно передать несколько параметров мы уже умеем это делать: первый параметр — путь (относительно текущей директории, из которой вы запускаете программу) к файлу, который нужно открыть, подробнее об этом в 2-м уроке, второй параметр — специальный ключ.

Этот ключ говорит классу File, как именно мы хотим открыть файл, и в какой кодировке мы этот файл хотим прочитать.

В нашем случае мы хотим открыть наш файл с афоризмами ./data/quotes.txt для чтения в кодировке UTF-8, поэтому пишем так:

file = File.new("./data/quotes.txt","r:UTF-8")

Обратите внимание на ключ "r:UTF-8", первая буква обозначает тип открытия файла:

  • r — только для чтения: мы будем только читать файл, писать в него мы так не сможем
  • w — только для записи: мы не хотим знать, что в файле, мы просто перепишем его содержимое
  • a — только для записи, но дописывать будем в конец файла, сам файл не трогаем

Есть и другие ключи, но в этом блоке не будем ими морочить вам голову, в этом уроке нам понадобится только первый, так что все остальные — для любознательных. После указания способа открытия файла через двоеточие идёт кодировка, в нашем случае UTF-8.

Чтение информации из файла

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

Мы можем получить всё содержимое файла в одной большой строке (со всеми словами, пробелами и переносами) с помощью метода read экземпляра класса File, то есть нашего объекта file.

content = file.read

Теперь по идее в переменной content у нас будут все цитаты одна за другой.

Чтение файла построчно

По условию задачи нам нужно прочитать только одну цитату, а не весь файл целиком. Поэтому нам нужно немного переписать нашу программу. Мы напишем новый файл read_lines.rb

file_path = "/data/aphorizmus.txt"

f = File.new(file_path, "r:UTF-8")
lines = f.readlines
puts lines.sample

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

Вместо file.read вызываем метод readlines, который в отличие от предыдущего возвращает не одну большую строку, а массив строк, разбитых по символам переноса.

Этот массив мы сохранили в переменную lines, поэтому чтобы вывести одну случайную строчку файла мы можем просто написать

puts lines.sample

Метод sample у массива возвращает один случайный элемент из этого массива — каждый раз разный.

Запуск программы

Программа написана и осталось её запустить, для этого как обычно переходим в нашу рабочую папку в консоли и запускаем программу с помощью команды ruby

cd c:\rubytut\lesson13\
ruby read_lines.rb

Что делать когда файл не найден?

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

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

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

Проверить наличие файла можно с помощью метода exist? у класса File:

if File.exist?(file_path)
  # тут можно работать с файлом, не боясь, что он не откроется
else
  puts "Файл не найден"
end

Как вы видите, метод очень похож на new, только никакой ключ ему не нужен, потому что методу exist? не важно, как с этим файлом собрались работать, ему только нужно проверить, есть ли файл и если файл есть, то вернуть true, а если нет — вернуть false.

Именно поэтому очень удобно использовать этот метод в качестве условия в конструкции if-else, если файл есть, мы его откроем и прочитаем, если его нет, мы напишем об этом пользователю и либо пойдём дальше, либо прекратим выполнение программы.

Теперь можно переименовать файл quotes.txt в, например, quotes_.txt и убедиться, что при запуске программы, мы увидим строчку

Файл не найден

Как правильно указывать пути к файлам

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

Если мы в консоли поднимемся на уровень выше:

cd ../

и снова запустим нашу программу, дописав к её пути спереди название папки, в которой она лежит (мы не говорили, но так можно делать):

ruby lesson13/qoute.rb

то встретимся как раз с ситуацией, когда файл не найден.

Наша проверка обломалась, ведь файла уже нет рядом с нами, он лежит в другой папке, о чём программа не знает, она ищет его в c:\rubytut\data, то есть рассматривает путь data относительно текущей папки консоли.

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

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

Это легко исправить, ведь для этого в Ruby есть классная штука: специальный объект __FILE__ — он содержит путь к файлу программы относительно той папки, из которой программа запущена. То есть откуда бы мы не запустили, с помощью объекта __FILE__ мы всегда можем восстановить правильный путь к файлам нашей программы.

Для того, чтобы из этой переменной получить путь к папке текущей программы, нам нужно снова обратиться к нашему классу File:

current_path = File.dirname(__FILE__)

Теперь в переменной current_path всегда (откуда бы мы ни запустили нашу программу) будет лежать путь к папке с этой программой.

А от него и до data/quotes.txt рукой подать, нужно просто склеить две строчки плюсиком, как мы это умеем делать:

file_path = current_path + "/data/quotes.txt"

Теперь откуда бы мы ни запустили нашу программу, она будет искать файл data/quotes.txt рядом с собой, а не в том месте, откуда её вызвали.

Это очень удобно. Можно в консоли перейти на папку выше и запустить программу вот так:

ruby lesson13/read_lines.rb

Тяжело в учении - легко в программировании!

Закрытие файлов

Если вкратце, то файлы нужно закрывать.

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

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

Для нашей маленькой программы это не так важно — Ruby сам закроет все файлы после выполнения программы.

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

Это как правило хорошего тона: подобно тому, как прилежный читатель всегда возвращает книгу туда, откуда он её взял — полка в собственном доме или библиотека, также и прилежный программист всегда должен помнить о том, что чем меньше на Земле незакрытых вовремя файлов, тем больше на Земле добра!

Каждый раз, когда вы забываете закрыть файл в вашей программе — где-то умирает котёнок. Спасайте котят, закрывайте файлы!

Делайте добро, Дамы и Господа, всегда закрывайте ваши файлы.

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

В нашей программе мы закроем файл ещё до того, как выведем строчку на экран.

current_path = File.dirname(__FILE__)
file_path = current_path + "/data/aphorizmus.txt"

if File.exist?(file_path)
  f = File.new(file_path, "r:UTF-8")
  lines = f.readlines
  f.close
  puts lines.sample
else
  puts "Файл не найден"
end

Итак, сегодня мы научились работать с файлами, узнали о классе File, как работают его методы new, dirname, exist? и методы его экземпляров read, readlines и close.

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

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