Протоколы http и WebSocket Часть 9 работа с файловой системой Часть 10 стандартные модули, потоки, базы данных, node env
Скачать 1.79 Mb.
|
Часть 9: работа с файловой системой Работа с файловыми дескрипторами в Node.js Прежде чем вы сможете взаимодействовать с файлами, находящимися в файловой системе вашего сервера, вам необходимо получить дескриптор файла. Дескриптор можно получить, воспользовавшись для открытия файла асинхронным методом open() из модуля fs: const fs = require('fs') fs.open('/Users/flavio/test.txt', 'r', (err, fd) => { //fd - это дескриптор файла }) Обратите внимание на второй параметр, r, использованный при вызове метода fs.open(). Это — флаг, который сообщает системе о том, что файл открывают для чтения. Вот ещё некоторые флаги, которые часто используются при работе с этим и некоторыми другими методами: ● r+ — открыть файл для чтения и для записи. ● w+ — открыть файл для чтения и для записи, установив указатель потока в начало файла. Если файл не существует — он создаётся. ● a — открыть файл для записи, установив указатель потока в конец файла. Если файл не существует — он создаётся. ● a+ — открыть файл для чтения и записи, установив указатель потока в конец файла. Если файл не существует — он создаётся. Файлы можно открывать и пользуясь синхронным методом fs.openSync(), который, вместо того, чтобы предоставить дескриптор файла в коллбэке, возвращает его: const fs = require('fs') try { const fd = fs.openSync('/Users/flavio/test.txt', 'r') } catch (err) { console.error(err) } После получения дескриптора любым из вышеописанных способов вы можете производить с ним необходимые операции. Данные о файлах С каждым файлом связан набор данных о нём, исследовать эти данные можно средствами Node.js. В частности, сделать это можно, используя метод stat() из модуля fs. Вызывают этот метод, передавая ему путь к файлу, и, после того, как Node.js получит необходимые сведения о файле, он вызовет коллбэк, переданный методу stat(). Вот как это выглядит: const fs = require('fs') fs.stat('/Users/flavio/test.txt', (err, stats) => { if (err) { console.error(err) return } //сведения о файле содержатся в аргументе `stats` }) В Node.js имеется возможность синхронного получения сведений о файлах. При таком подходе главный поток блокируется до получения свойств файла: const fs = require('fs') try { const stats = fs.statSync ('/Users/flavio/test.txt') } catch (err) { console.error(err) } Информация о файле попадёт в константу stats. Что это за информация? На самом деле, соответствующий объект предоставляет нам большое количество полезных свойств и методов: ● Методы .isFile() и .isDirectory() позволяют, соответственно, узнать, является ли исследуемый файл обычным файлом или директорией. ● Метод .isSymbolicLink() позволяет узнать, является ли файл символической ссылкой. ● Размер файла можно узнать, воспользовавшись свойством .size. Тут имеются и другие методы, но эти — самые употребимые. Вот как ими пользоваться: const fs = require('fs') fs.stat('/Users/flavio/test.txt', (err, stats) => { if (err) { console.error(err) return } stats.isFile() //true stats.isDirectory() //false stats.isSymbolicLink() //false stats.size //1024000 //= 1MB }) Пути к файлам в Node.js и модуль path Путь к файлу — это адрес того места в файловой системе, где он расположен. В Linux и macOS путь может выглядеть так: /users/flavio/file.txt В Windows пути выглядят немного иначе: C:\users\flavio\file.txt На различия в форматах записи путей при использовании разных операционных систем следует обращать внимание, учитывая операционную систему, используемую для развёртывания Node.js-сервера. В Node.js есть стандартный модуль path, предназначенный для работы с путями к файлам. Перед использованием этого модуля в программе его надо подключить: const path = require('path') Получение информации о пути к файлу Если у вас есть путь к файлу, то, используя возможности модуля path, вы можете, в удобном для восприятия и дальнейшей обработки виде, узнать подробности об этом пути. Выглядит это так: const notes = '/users/flavio/notes.txt' path.dirname(notes) // /users/flavio path.basename(notes) // notes.txt path.extname(notes) // .txt Здесь, в строке notes, хранится путь к файлу. Для разбора пути использованы следующие методы модуля path: ● dirname() — возвращает родительскую директорию файла. ● basename() — возвращает имя файла. ● extname() — возвращает расширение файла. Узнать имя файла без расширения можно, вызвав метод .basename() и передав ему второй аргумент, представляющий расширение: path.basename(notes, path.extname(notes)) //notes Работа с путями к файлам Несколько частей пути можно объединить, используя метод path.join(): const name = 'flavio' path.join('/', 'users', name, 'notes.txt') //'/users/flavio/notes.txt' Найти абсолютный путь к файлу на основе относительного пути к нему можно с использованием метода path.resolve(): path.resolve('flavio.txt') //'/Users/flavio/flavio.txt' при запуске из моей домашней папки В данном случае Node.js просто добавляет /flavio.txt к пути, ведущем к текущей рабочей директории. Если при вызове этого метода передать ещё один параметр, представляющий путь к папке, метод использует его в качестве базы для определения абсолютного пути: path.resolve('tmp', 'flavio.txt') // '/Users/flavio/tmp/flavio.txt' при запуске из моей домашней папки Если путь, переданный в качестве первого параметра, начинается с косой черты — это означает, что он представляет собой абсолютный путь. path.resolve('/etc', 'flavio.txt') // '/etc/flavio.txt' Вот ещё один полезный метод — path.normalize(). Он позволяет найти реальный путь к файлу, используя путь, в котором содержатся спецификаторы относительного пути вроде точки (.), двух точек (..), или двух косых черт: path.normalize('/users/flavio/..//test.txt') // /users/test.txt Методы resolve() и normalize() не проверяют существование директории. Они просто находят путь, основываясь на переданным им данным. Чтение файлов в Node.js Самый простой способ чтения файлов в Node.js заключается в использовании метода fs.readFile() с передачей ему пути к файлу и коллбэка, который будет вызван с передачей ему данных файла (или объекта ошибки): fs.readFile('/Users/flavio/test.txt', (err, data) => { if (err) { console.error(err) return } console.log(data) }) Если надо, можно воспользоваться синхронной версией этого метода — fs.readFileSync(): const fs = require('fs') try { const data = fs.readFileSync('/Users/flavio/test.txt') console.log(data) } catch (err) { console.error(err) } По умолчанию при чтении файлов используется кодировка utf8, но кодировку можно задать и самостоятельно, передав методу соответствующий параметр. Методы fs.readFile() и fs.readFileSync() считывают в память всё содержимое файла. Это означает, что работа с большими файлами с применением этих методов серьёзно отразится на потреблении памяти вашим приложением и окажет влияние на его производительность. Если с такими файлами нужно работать, лучше всего воспользоваться потоками. Запись файлов в Node.js В Node.js легче всего записывать файлы с использованием метода fs.writeFile(): const fs = require('fs') const content = 'Some content!' fs.writeFile('/Users/flavio/test.txt', content, (err) => { if (err) { console.error(err) return } //файл записан успешно }) Есть и синхронная версия того же метода — fs.writeFileSync(): const fs = require('fs') const content = 'Some content!' try { const data = fs.writeFileSync('/Users/flavio/test.txt', content) //файл записан успешно } catch (err) { console.error(err) } Эти методы, по умолчанию, заменяют содержимое существующих файлов. Изменить их стандартное поведение можно, воспользовавшись соответствующим флагом: fs.writeFile('/Users/flavio/test.txt', content, { flag: 'a+' }, (err) => {}) Тут могут использоваться флаги, которые мы уже перечисляли в разделе, посвящённом дескрипторам. Подробности о флагах можно узнать здесь Присоединение данных к файлу Метод fs.appendFile() (и его синхронную версию — fs.appendFileSync()) удобно использовать для присоединения данных к концу файла: const content = 'Some content!' fs.appendFile('file.log', content, (err) => { if (err) { console.error(err) return } //готово! }) Об использовании потоков Выше мы описывали методы, которые, выполняя запись в файл, пишут в него весь объём переданных им данных, после чего, если используются их синхронные версии, возвращают управление программе, а если применяются асинхронные версии — вызывают коллбэки. Если вас такое состояние дел не устраивает — лучше будет воспользоваться потоками. Работа с директориями в Node.js Модуль fs предоставляет в распоряжение разработчика много удобных методов, которые можно использовать для работы с директориями. Проверка существования папки Для того чтобы проверить, существует ли директория и может ли Node.js получить к ней доступ, учитывая разрешения, можно использовать метод fs.access(). Создание новой папки Для того чтобы создавать новые папки, можно воспользоваться методами fs.mkdir() и fs.mkdirSync(): const fs = require('fs') const folderName = '/Users/flavio/test' try { if (!fs.existsSync(dir)){ fs.mkdirSync(dir) } } catch (err) { console.error(err) } Чтение содержимого папки Для того чтобы прочесть содержимое папки, можно воспользоваться методами fs.readdir() и fs.readdirSync(). В этом примере осуществляется чтение содержимого папки — то есть — сведений о том, какие файлы и поддиректории в ней имеются, и возврат их относительных путей: const fs = require('fs') const path = require('path') const folderPath = '/Users/flavio' fs.readdirSync(folderPath) Вот так можно получить полный путь к файлу: fs.readdirSync(folderPath).map(fileName => { return path.join(folderPath, fileName) } Результаты можно отфильтровать для того, чтобы получить только файлы и исключить из вывода директории: const isFile = fileName => { return fs.lstatSync(fileName).isFile() } fs.readdirSync(folderPath).map(fileName => { return path.join(folderPath, fileName)).filter(isFile) } Переименование папки Для переименования папки можно воспользоваться методами fs.rename() и fs.renameSync(). Первый параметр — это текущий путь к папке, второй — новый: const fs = require('fs') fs.rename('/Users/flavio', '/Users/roger', (err) => { if (err) { console.error(err) return } //готово }) Переименовать папку можно и с помощью синхронного метода fs.renameSync(): const fs = require('fs') try { fs.renameSync('/Users/flavio', '/Users/roger') } catch (err) { console.error(err) } Удаление папки Для того чтобы удалить папку, можно воспользоваться методами fs.rmdir() или fs.rmdirSync(). Надо отметить, что удаление папки, в которой что-то есть, задача несколько более сложная, чем удаление пустой папки. Если вам нужно удалять такие папки, воспользуйтесь пакетом fs-extra , который весьма популярен и хорошо поддерживается. Он представляет собой замену модуля fs, расширяющую его возможности. Метод remove() из пакета fs-extra умеет удалять папки, в которых уже что-то есть. Установить этот модуль можно так: npm install fs-extra Вот пример его использования: const fs = require('fs-extra') const folder = '/Users/flavio' fs.remove(folder, err => { console.error(err) }) Его методами можно пользоваться в виде промисов: fs.remove(folder).then(() => { //готово }).catch(err => { console.error(err) }) Допустимо и применение конструкции async/await: async function removeFolder(folder) { try { await fs.remove(folder) //готово } catch (err) { console.error(err) } } const folder = '/Users/flavio' removeFolder(folder) Модуль fs Выше мы уже сталкивались с некоторыми методами модуля fs, применяемыми при работе с файловой системой. На самом деле, он содержит ещё много полезного. Напомним, что он не нуждается в установке, для того, чтобы воспользоваться им в программе, его достаточно подключить: const fs = require('fs') После этого у вас будет доступ к его методам, среди которых отметим следующие, некоторые из которых вам уже знакомы: ● fs.access(): проверяет существование файла и возможность доступа к нему с учётом разрешений. ● fs.appendFile(): присоединяет данные к файлу. Если файл не существует — он будет создан. ● fs.chmod(): изменяет разрешения для заданного файла. Похожие методы: fs.lchmod(), fs.fchmod(). ● fs.chown(): изменяет владельца и группу для заданного файла. Похожие методы: fs.fchown(), fs.lchown(). ● fs.close(): закрывает дескриптор файла. ● fs.copyFile(): копирует файл. ● fs.createReadStream(): создаёт поток чтения файла. ● fs.createWriteStream(): создаёт поток записи файла. ● fs.link(): создаёт новую жёсткую ссылку на файл. ● fs.mkdir(): создаёт новую директорию. ● fs.mkdtemp(): создаёт временную директорию. ● fs.open(): открывает файл. ● fs.readdir(): читает содержимое директории. ● fs.readFile(): считывает содержимое файла. Похожий метод: fs.read(). ● fs.readlink(): считывает значение символической ссылки. ● fs.realpath(): разрешает относительный путь к файлу, построенный с использованием символов . и .., в полный путь. ● fs.rename(): переименовывает файл или папку. ● fs.rmdir(): удаляет папку. ● fs.stat(): возвращает сведения о файле. Похожие методы: fs.fstat(), fs.lstat(). ● fs.symlink(): создаёт новую символическую ссылку на файл. ● fs.truncate(): обрезает файл до заданной длины. Похожий метод: fs.ftruncate(). ● fs.unlink(): удаляет файл или символическую ссылку. ● fs.unwatchFile(): отключает наблюдение за изменениями файла. ● fs.utimes(): изменяет временную отметку файла. Похожий метод: fs.futimes(). ● fs.watchFile(): включает наблюдение за изменениями файла. Похожий метод: fs.watch(). ● fs.writeFile(): записывает данные в файл. Похожий метод: fs.write(). Интересной особенностью модуля fs является тот факт, что все его методы, по умолчанию, являются асинхронными, но существуют и их синхронные версии, имена которых получаются путём добавления слова Sync к именам асинхронных методов. Например: ● fs.rename() ● fs.renameSync() ● fs.write() ● fs.writeSync() Использование синхронных методов серьёзно влияет на то, как работает программа. В Node.js 10 имеется экспериментальная поддержка этих API , основанных на промисах. Исследуем метод fs.rename(). Вот асинхронная версия этого метода, использующая коллбэки: const fs = require('fs') fs.rename('before.json', 'after.json', (err) => { if (err) { return console.error(err) } //готово }) При использовании его синхронной версии для обработки ошибок используется конструкция try/catch: const fs = require('fs') try { fs.renameSync('before.json', 'after.json') //готово } catch (err) { console.error(err) } Основное различие между этими вариантами использования данного метода заключается в том, что во втором случае выполнение скрипта будет заблокировано до завершения файловой операции. Модуль path Модуль path, о некоторых возможностях которого мы тоже уже говорили, содержит множество полезных инструментов, позволяющих взаимодействовать с файловой системой. Как уже было сказано, устанавливать его не нужно, так как он является частью Node.js. Для того чтобы пользоваться им, его достаточно подключить: const path = require('path') Свойство path.sep этого модуля предоставляет символ, использующийся для разделения сегментов пути (\ в Windows и / в Linux и macOS), а свойство path.delimiter даёт символ, используемый для отделения друг от друга нескольких путей (; в Windows и : в Linux и macOS). Рассмотрим и проиллюстрируем примерами некоторые методы модуля path. path.basename() Возвращает последний фрагмент пути. Передав второй параметр этому методу можно убрать расширение файла. require('path').basename('/test/something') //something require('path').basename('/test/something.txt') //something.txt require('path').basename('/test/something.txt', '.txt') //something path.dirname() Возвращает ту часть пути, которая представляет имя директории: require('path').dirname('/test/something') // /test require('path').dirname('/test/something/file.txt') // /test/something path.extname() Возвращает ту часть пути, которая представляет расширение файла: require('path').dirname('/test/something') // '' require('path').dirname('/test/something/file.txt') // '.txt' path.isAbsolute() Возвращает истинное значение если путь является абсолютным: require('path').isAbsolute('/test/something') // true require('path').isAbsolute('./test/something') // false path.join() Соединяет несколько частей пути: const name = 'flavio' require('path').join('/', 'users', name, 'notes.txt') //'/users/flavio/notes.txt' path.normalize() Пытается выяснить реальный путь на основе пути, который содержит символы, использующиеся при построении относительных путей вроде ., .. и //: require('path').normalize('/users/flavio/..//test.txt') ///users/test.txt path.parse() Преобразует путь в объект, свойства которого представляют отдельные части пути: ● root: корневая директория. ● dir: путь к файлу, начиная от корневой директории ● base: имя файла и расширение. ● name: имя файла. ● ext: расширение файла. Вот пример использования этого метода: require('path').parse('/users/test.txt') В результате его работы получается такой объект: { root: '/', dir: '/users', base: 'test.txt', ext: '.txt', name: 'test' } path.relative() Принимает, в качестве аргументов, 2 пути. Возвращает относительный путь из первого пути ко второму, основываясь на текущей рабочей директории: require('path').relative('/Users/flavio', '/Users/flavio/test.txt') //'test.txt' |