- PVSM.RU - https://www.pvsm.ru -
Сегодня, в девятой части перевода руководства по Node.js, мы поговорим о работе с файлами. В частности, речь пойдёт о модулях fs и path — о файловых дескрипторах, о путях к файлам, о получении информации о файлах, об их чтении и записи, о работе с директориями.
Прежде чем вы сможете взаимодействовать с файлами, находящимися в файловой системе вашего сервера, вам необходимо получить дескриптор файла.
Дескриптор можно получить, воспользовавшись для открытия файла асинхронным методом 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
})
Путь к файлу — это адрес того места в файловой системе, где он расположен.
В Linux и macOS путь может выглядеть так:
/users/flavio/file.txt
В Windows пути выглядят немного иначе:
C:usersflaviofile.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 заключается в использовании метода 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 легче всего записывать файлы с использованием метода 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) => {})
Тут могут использоваться флаги, которые мы уже перечисляли в разделе, посвящённом дескрипторам. Подробности о флагах можно узнать здесь [10].
Метод fs.appendFile()
(и его синхронную версию — fs.appendFileSync()
) удобно использовать для присоединения данных к концу файла:
const content = 'Some content!'
fs.appendFile('file.log', content, (err) => {
if (err) {
console.error(err)
return
}
//готово!
})
Выше мы описывали методы, которые, выполняя запись в файл, пишут в него весь объём переданных им данных, после чего, если используются их синхронные версии, возвращают управление программе, а если применяются асинхронные версии — вызывают коллбэки. Если вас такое состояние дел не устраивает — лучше будет воспользоваться потоками.
Модуль 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 [11], который весьма популярен и хорошо поддерживается. Он представляет собой замену модуля 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
, применяемыми при работе с файловой системой. На самом деле, он содержит ещё много полезного. Напомним, что он не нуждается в установке, для того, чтобы воспользоваться им в программе, его достаточно подключить:
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 [12], основанных на промисах.
Исследуем метод 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, о некоторых возможностях которого мы тоже уже говорили, содержит множество полезных инструментов, позволяющих взаимодействовать с файловой системой. Как уже было сказано, устанавливать его не нужно, так как он является частью Node.js. Для того чтобы пользоваться им, его достаточно подключить:
const path = require('path')
Свойство path.sep
этого модуля предоставляет символ, использующийся для разделения сегментов пути ( в Windows и
/
в Linux и macOS), а свойство path.delimiter
даёт символ, используемый для отделения друг от друга нескольких путей (;
в Windows и :
в Linux и macOS).
Рассмотрим и проиллюстрируем примерами некоторые методы модуля path
.
Возвращает последний фрагмент пути. Передав второй параметр этому методу можно убрать расширение файла.
require('path').basename('/test/something') //something
require('path').basename('/test/something.txt') //something.txt
require('path').basename('/test/something.txt', '.txt') //something
Возвращает ту часть пути, которая представляет имя директории:
require('path').dirname('/test/something') // /test
require('path').dirname('/test/something/file.txt') // /test/something
<h3><font color="#000">▍path.extname()</font></h3>
</code>Возвращает ту часть пути, которая представляет расширение файла:
<source>require('path').dirname('/test/something') // ''
require('path').dirname('/test/something/file.txt') // '.txt'
Возвращает истинное значение если путь является абсолютным:
require('path').isAbsolute('/test/something') // true
require('path').isAbsolute('./test/something') // false
Соединяет несколько частей пути:
const name = 'flavio'
require('path').join('/', 'users', name, 'notes.txt') //'/users/flavio/notes.txt'
Пытается выяснить реальный путь на основе пути, который содержит символы, использующиеся при построении относительных путей вроде .
, ..
и //
:
require('path').normalize('/users/flavio/..//test.txt') ///users/test.txt
Преобразует путь в объект, свойства которого представляют отдельные части пути:
root
: корневая директория.dir
: путь к файлу, начиная от корневой директорииbase
: имя файла и расширение.name
: имя файла.ext
: расширение файла.Вот пример использования этого метода:
require('path').parse('/users/test.txt')
В результате его работы получается такой объект:
{
root: '/',
dir: '/users',
base: 'test.txt',
ext: '.txt',
name: 'test'
}
Принимает, в качестве аргументов, 2 пути. Возвращает относительный путь из первого пути ко второму, основываясь на текущей рабочей директории:
require('path').relative('/Users/flavio', '/Users/flavio/test.txt') //'test.txt'
require('path').relative('/Users/flavio', '/Users/flavio/something/test.txt') //'something/test.txt'
Находит абсолютный путь на основе переданного ему относительного пути:
path.resolve('flavio.txt')
//'/Users/flavio/flavio.txt' при запуске из моей домашней папки.
Сегодня мы рассмотрели модули Node.js fs
и path
, которые используются для работы с файловой системой. В следующей части этой серии, на которой она завершается, мы обсудим модули os
, events
, http
, поговорим о работе с потоками и с системами управления базами данных в Node.js.
Уважаемые читатели! Какими npm-пакетами вы пользуетесь при работе с файловой системой в Node.js?
Автор: ru_vds
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/294657
Ссылки в тексте:
[1] Image: https://habr.com/company/ruvds/blog/424969/
[2] Общие сведения и начало работы: https://habr.com/company/ruvds/blog/422893/
[3] JavaScript, V8, некоторые приёмы разработки: https://habr.com/company/ruvds/blog/423153/
[4] Хостинг, REPL, работа с консолью, модули: https://habr.com/company/ruvds/blog/423701/
[5] npm, файлы package.json и package-lock.json: https://habr.com/company/ruvds/blog/423703/
[6] npm и npx: https://habr.com/company/ruvds/blog/423705/
[7] цикл событий, стек вызовов, таймеры: https://habr.com/company/ruvds/blog/424553/
[8] асинхронное программирование: https://habr.com/company/ruvds/blog/424555/
[9] Руководство по Node.js, часть 8: протоколы HTTP и WebSocket: https://habr.com/company/ruvds/blog/424557/
[10] здесь: https://nodejs.org/api/fs.html#fs_file_system_flags
[11] fs-extra: https://www.npmjs.com/package/fs-extra
[12] API: https://nodejs.org/api/fs.html#fs_fs_promises_api
[13] Источник: https://habr.com/post/424969/?utm_campaign=424969
Нажмите здесь для печати.