Всем привет! Цель этого занятия – научиться работать с бинарными данными и с файловой системой. В Node.js для работы с файлами существует модуль fs, и в нем есть множество функций для самых различных операций с файлами и директориями.
Если мы приглядимся внимательно, то увидим первую особенность этого модуля. Почти все функции имеют два варианта, первое – просто имя, второе – имя со словом Sync. Слово Sync означает синхронно. Если я, например, вызову fs.readFile, то fs.readFile сначала прочитает полностью, а потом вызовет callback (fs.readFile(filename,[options], callback), а fs.readFileSync затормозит выполнение процесса Node.js, пока файл не будет прочитан. Поэтому, как правило, синхронный вызов используется либо в консольных утилитах, либо на стадии инициализации сервера, когда такие тормоза вполне допустимы, а асинхронный вызов в тех случаях, когда хочется, чтобы полноценно работал событийный цикл, то есть, чтобы Node.js не ждал, пока медленный диск сработает, и файл прочитается.
Посмотрим на реальный пример использования. Создадим файла read.js. Подключаем модуль fs и вызываю асинхронную функцию readFile:
var fs = require('fs'); fs.readFile(__filename, function(err, data) { if (err) { console.error(err); } else { console.log(data); } });
Эта функция принимает имя файла, в данном случае, это путь к текущему файлу модуля, и получает callback: первый аргумент, как всегда, ошибка, второй – данные, содержимое файла. Если бы это был асинхронный вызов, это выглядело бы так:
var fs = require('fs'); var data = fs.readFileSync(__filename); fs.readFile(__filename, function(err, data) { if (err) { console.error(err); } else { console.log(data); } });
При этом, в случае ошибки было бы исключение. Но мы здесь дальше будем работать с асинхронными вызовами. Итак, запускаем. Обратите внимание, вывелось не содержимое файла в виде строки, а специальный объект-буфер. Этот объект-буфер, который является высокоэффективным средством Node.js для работы с бинарными данными. Технически буфер – это непрерывная область памяти, которая, в данном случае, заполнена этими данными.
Работа с буфером достаточно похожа на работу со строкой. Здесь можно получить нулевой элемент:
var fs = require('fs'); fs.readFile(__filename, function(err, data) { if (err) { console.error(err); } else { console.log(data); data[0] } });
Можно получить длину буфера:
var fs = require('fs'); fs.readFile(__filename, function(err, data) { if (err) { console.error(err); } else { console.log(data); data.length } });
Но, в отличие от строк, которые в JavaScript совершенно неизменяемы, содержимое буфера можно менять. Для этого в документации предусмотрено ряд методов, начиная от простейшего write, который пишет в буфер строку, преобразуя ее в бинарный формат, учитывая кодировку encoding, и заканчивая различными методами, которые записывают в буфер целые числа, дробные числа, числа в формате double и другие числа, учитывая внутреннее компьютерное двоичное представление данных форматов.
В данном случае мы хотели бы вывести содержимое файла в виде строки, поэтому давайте преобразуем буфер-строку вызовом toString, в скобках указав кодировку, то есть, таблицу, которая указывает, как преобразовать байты в символы алфавита. По умолчанию это utf-8, но мы так ее и оставим.
var fs = require('fs'); fs.readFile(__filename, function(err, data) { if (err) { console.error(err); } else { console.log(data.toString()); } });
Запускаем. Если я точно знаю, что работаю со строками, то я, например, могу указать кодировку прямо здесь:
fs.readFile(__filename, {encoding: 'utf-8'} function(err, data) {
Запускаем. Тоже работает. В этом случае преобразование в строку происходит непосредственно внутри функции readFile.
А теперь давайте посмотрим, что происходит, если где-то ошибка. Например, я считываю файл, которого не существует:
var fs = require('fs'); fs.readFile("blablabla", {encoding: 'utf-8'}, function(err, data) { if (err) { console.error(err); } else { console.log(data); } });
Запускаю. Вывелась ошибка в console.error.
Обращаю ваше внимание, что в ошибке есть следующие данные. Во-первых, это имя ошибки. В данном случае, ‘ENOENT’ означает, что файла нет. Во-вторых, это цифровой код: error 34. Оба кода являются полностью cross-paltform, то есть, неважно под Windows, Linux или еще под чем-то я нахожусь, всегда если файл не найден, то это означает ошибка ‘ENOENT’. Соответственно, можем проверить, если код ENOENT, то среагирует одним способом, а иначе – сделает что-то еще:
var fs = require('fs'); fs.readFile("blablabla", {encoding: 'utf-8'}, function(err, data) { if (err) { if (err.code == 'ENOENT') { console.error(err.message); } else { console.error(err); } } else { console.log(data); } });
Запускаем. Вот. Все сработало.
Если в будущем вас вдруг заинтересует, какие еще ошибки есть, или вы захотите получить расшифровку кода ошибки, то, к сожалению, в документации Node.js эта информация отсутствует. Но вы найдете ее в исходнике библиотеки libUV, в данном случае, в файле uv.h находятся различные виды ошибок, например, ENOENT или EACCES. Эти коды находятся именно здесь, потому что за ввод-вывод отвечает библиотека libUV. Она трансформирует различные коды операционных систем в cross-platform значения. Если мы заведомо знаем, что файл может не существовать, то можем проверить его наличие при помощи специального вызова. Для этого есть, во-первых, вызов fs.exists он проверяет, есть ли определенный путь, но он не может проверить, что это такое файл или директория. Для более точных проверок есть вызов fs.stat и различные его варианты, которые вы можете более подробно изучить в документации.
Как правило, в большинстве ситуаций подходит просто stat. Он получает путь и возвращает объект специального типа fs.Stats, который содержит подробную информацию о том, что по нему находится. Вот пример его использования:
var fs = require('fs'); fs.stat(__filename, function(err, stats) { console.log(stats.isFile()); console.log(stats); });
Запускаю. Console.log первый выявил true, потому что это файл, а второй вывел полную информацию о том, что находится по данному пути. Это немного зависит от операционной системы, файловой системы, но практически всегда есть размер size, а также mtime и дата создания ctime.
А вот пример создания нового файла, в котором будет содержаться строка data, после чего мы его переименовываем.
var fs = require('fs'); fs.writeFile("file.tmp", "data", function(err) { if (err) throw err; fs.rename("file.tmp", "new.tmp", function(err) { if (err) throw err; fs.unlink("new.tmp", function(err) { if (err) throw err; }); });
А после переименовывания удаляем. Обратите внимание, что в каждом callback мы проверяем ошибку. То есть, после того, как файл создан, мы обязательно проверяем, если есть ошибка, нужно ее как-то обработать. Самый простейший способ – это throw. Дальше, переименовали, обработали. В каждом callback должна быть предусмотрена выработка ошибок, потому что ошибки могут быть в самых непредсказуемых местах. Если пропустить, то потом может быть совершенно непонятно, почему оно перестало работать, и где эту самую ошибку искать.
Итак, мы кратко познакомились с основными возможностями модуля fs и с некоторыми примерами их применения. Вообще, у этого модуля есть очень много методов. Мы рекомендуем посмотреть их в документации, просто чтобы понимать, что они вообще существуют.
Код урока доступен здесь.
Материалы статьи взяты из данного скринкаста.
We are looking forward to meeting you on our website soshace.com
Подскажите, а можно ли взять вторую строку в переменную, и собственно эту же строку удалить из файла?
Я думаю можно прочитать файл, распарсить и удалить оттуда строку и уже перезаписывать файл.