- Home »

Как создавать, читать и записывать файлы с помощью модуля fs в Node.js
Работа с файловой системой в Node.js — это каждодневная задача для любого разработчика, который имеет дело с серверными приложениями. Модуль fs (File System) является одним из основных встроенных модулей Node.js, позволяющим создавать, читать, записывать и манипулировать файлами напрямую из JavaScript-кода. Если вы администрируете серверы, автоматизируете процессы развёртывания или создаёте утилиты для управления конфигурационными файлами, знание fs откроет для вас массу возможностей.
Этот модуль станет незаменимым инструментом при создании скриптов для резервного копирования логов, автоматического парсинга конфигов, генерации отчётов или обработки больших объёмов данных. В отличие от браузерного JavaScript, Node.js предоставляет полный доступ к файловой системе операционной системы, что делает его мощным инструментом для серверной автоматизации.
Основы работы с модулем fs
Модуль fs предоставляет как синхронные, так и асинхронные методы для работы с файлами. Большинство асинхронных методов имеют синхронные аналоги с суффиксом “Sync”, а также поддерживают промисы через fs.promises.
Основные операции, которые мы рассмотрим:
- Создание и проверка существования файлов
- Чтение файлов различными способами
- Запись и добавление данных в файлы
- Работа с директориями
- Потоковое чтение и запись для больших файлов
Импорт и основные методы
Для начала работы необходимо импортировать модуль fs:
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
// Или использовать промисы напрямую
const fsPromises = require('fs').promises;
// В современном Node.js (14+) можно использовать import
// import { promises as fs } from 'fs';
Создание и проверка файлов
Перед работой с файлами часто нужно проверить их существование и создать при необходимости:
const fs = require('fs');
const path = require('path');
// Проверка существования файла
function checkFileExists(filePath) {
return fs.existsSync(filePath);
}
// Создание пустого файла
function createFile(filePath) {
if (!checkFileExists(filePath)) {
fs.writeFileSync(filePath, '');
console.log(`Файл ${filePath} создан`);
} else {
console.log(`Файл ${filePath} уже существует`);
}
}
// Создание директории если не существует
function ensureDirectoryExists(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(`Директория ${dirPath} создана`);
}
}
// Пример использования
const configDir = './config';
const configFile = path.join(configDir, 'server.conf');
ensureDirectoryExists(configDir);
createFile(configFile);
Чтение файлов: синхронно и асинхронно
Существует несколько способов чтения файлов в зависимости от размера данных и требований к производительности:
const fs = require('fs');
// 1. Синхронное чтение (блокирующее)
function readFileSync(filePath) {
try {
const data = fs.readFileSync(filePath, 'utf8');
return data;
} catch (error) {
console.error('Ошибка при чтении файла:', error.message);
return null;
}
}
// 2. Асинхронное чтение с callback
function readFileAsync(filePath, callback) {
fs.readFile(filePath, 'utf8', (error, data) => {
if (error) {
console.error('Ошибка при чтении файла:', error.message);
callback(error, null);
return;
}
callback(null, data);
});
}
// 3. Асинхронное чтение с промисами
async function readFilePromise(filePath) {
try {
const data = await fs.promises.readFile(filePath, 'utf8');
return data;
} catch (error) {
console.error('Ошибка при чтении файла:', error.message);
return null;
}
}
// 4. Чтение больших файлов по частям
function readLargeFile(filePath) {
const stream = fs.createReadStream(filePath, {
encoding: 'utf8',
highWaterMark: 16 * 1024 // 16KB chunks
});
let data = '';
stream.on('data', chunk => {
data += chunk;
console.log(`Прочитано ${chunk.length} байт`);
});
stream.on('end', () => {
console.log('Чтение файла завершено');
console.log(`Общий размер: ${data.length} символов`);
});
stream.on('error', error => {
console.error('Ошибка при чтении:', error.message);
});
}
// Примеры использования
console.log('=== Синхронное чтение ===');
const syncData = readFileSync('./package.json');
console.log(syncData);
console.log('=== Асинхронное чтение ===');
readFileAsync('./package.json', (error, data) => {
if (!error) {
console.log(data);
}
});
console.log('=== Чтение с промисами ===');
readFilePromise('./package.json').then(data => {
if (data) {
console.log(data);
}
});
Запись файлов: различные подходы
Запись файлов также может выполняться различными способами в зависимости от требований:
const fs = require('fs');
const path = require('path');
// 1. Синхронная запись (перезаписывает файл)
function writeFileSync(filePath, data) {
try {
fs.writeFileSync(filePath, data, 'utf8');
console.log(`Файл ${filePath} успешно записан`);
return true;
} catch (error) {
console.error('Ошибка при записи файла:', error.message);
return false;
}
}
// 2. Асинхронная запись с callback
function writeFileAsync(filePath, data, callback) {
fs.writeFile(filePath, data, 'utf8', (error) => {
if (error) {
console.error('Ошибка при записи файла:', error.message);
callback(error);
return;
}
console.log(`Файл ${filePath} успешно записан`);
callback(null);
});
}
// 3. Запись с промисами
async function writeFilePromise(filePath, data) {
try {
await fs.promises.writeFile(filePath, data, 'utf8');
console.log(`Файл ${filePath} успешно записан`);
return true;
} catch (error) {
console.error('Ошибка при записи файла:', error.message);
return false;
}
}
// 4. Добавление данных в конец файла
function appendToFile(filePath, data) {
try {
fs.appendFileSync(filePath, data + '\n', 'utf8');
console.log(`Данные добавлены в файл ${filePath}`);
return true;
} catch (error) {
console.error('Ошибка при добавлении данных:', error.message);
return false;
}
}
// 5. Потоковая запись для больших объёмов данных
function writeStreamExample(filePath, dataArray) {
const writeStream = fs.createWriteStream(filePath, {
encoding: 'utf8',
flags: 'w' // 'w' для перезаписи, 'a' для добавления
});
writeStream.on('error', (error) => {
console.error('Ошибка при записи потока:', error.message);
});
writeStream.on('finish', () => {
console.log('Потоковая запись завершена');
});
// Записываем данные по частям
dataArray.forEach((item, index) => {
writeStream.write(`${index}: ${item}\n`);
});
writeStream.end(); // Завершаем запись
}
// Примеры использования
const testData = JSON.stringify({
timestamp: new Date().toISOString(),
message: 'Тестовые данные',
server: 'web-01'
}, null, 2);
console.log('=== Синхронная запись ===');
writeFileSync('./test-sync.json', testData);
console.log('=== Асинхронная запись ===');
writeFileAsync('./test-async.json', testData, (error) => {
if (!error) {
console.log('Асинхронная запись завершена');
}
});
console.log('=== Запись с промисами ===');
writeFilePromise('./test-promise.json', testData);
console.log('=== Добавление в файл ===');
appendToFile('./server.log', `${new Date().toISOString()} - Сервер запущен`);
console.log('=== Потоковая запись ===');
const bigDataArray = Array.from({length: 1000}, (_, i) => `Запись номер ${i}`);
writeStreamExample('./big-data.txt', bigDataArray);
Практические примеры и кейсы
Рассмотрим несколько практических сценариев использования модуля fs в серверном администрировании:
Пример 1: Парсинг и обновление конфигурационных файлов
const fs = require('fs');
const path = require('path');
class ConfigManager {
constructor(configPath) {
this.configPath = configPath;
this.config = {};
this.loadConfig();
}
loadConfig() {
try {
if (fs.existsSync(this.configPath)) {
const data = fs.readFileSync(this.configPath, 'utf8');
this.config = JSON.parse(data);
console.log('Конфигурация загружена');
} else {
console.log('Файл конфигурации не найден, создаём новый');
this.config = this.getDefaultConfig();
this.saveConfig();
}
} catch (error) {
console.error('Ошибка при загрузке конфигурации:', error.message);
this.config = this.getDefaultConfig();
}
}
getDefaultConfig() {
return {
server: {
port: 3000,
host: '0.0.0.0'
},
database: {
host: 'localhost',
port: 5432,
name: 'app_db'
},
logging: {
level: 'info',
file: './logs/app.log'
}
};
}
saveConfig() {
try {
const configDir = path.dirname(this.configPath);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), 'utf8');
console.log('Конфигурация сохранена');
return true;
} catch (error) {
console.error('Ошибка при сохранении конфигурации:', error.message);
return false;
}
}
get(key) {
return key.split('.').reduce((obj, k) => obj?.[k], this.config);
}
set(key, value) {
const keys = key.split('.');
const lastKey = keys.pop();
const target = keys.reduce((obj, k) => obj[k] = obj[k] || {}, this.config);
target[lastKey] = value;
this.saveConfig();
}
reload() {
this.loadConfig();
}
}
// Использование
const config = new ConfigManager('./config/server.json');
console.log('Порт сервера:', config.get('server.port'));
config.set('server.port', 8080);
console.log('Новый порт:', config.get('server.port'));
Пример 2: Система логирования
const fs = require('fs');
const path = require('path');
class Logger {
constructor(options = {}) {
this.logDir = options.logDir || './logs';
this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB
this.maxFiles = options.maxFiles || 5;
this.level = options.level || 'info';
this.levels = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
this.ensureLogDirectory();
}
ensureLogDirectory() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
getLogFilePath(level = 'app') {
const date = new Date().toISOString().split('T')[0];
return path.join(this.logDir, `${level}-${date}.log`);
}
shouldLog(level) {
return this.levels[level] <= this.levels[this.level];
}
rotateLogFile(filePath) {
try {
const stats = fs.statSync(filePath);
if (stats.size > this.maxFileSize) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const rotatedPath = filePath.replace('.log', `-${timestamp}.log`);
fs.renameSync(filePath, rotatedPath);
// Удаляем старые файлы
this.cleanupOldLogs();
return true;
}
} catch (error) {
// Файл не существует, ничего не делаем
}
return false;
}
cleanupOldLogs() {
try {
const files = fs.readdirSync(this.logDir)
.filter(file => file.endsWith('.log'))
.map(file => ({
name: file,
path: path.join(this.logDir, file),
stats: fs.statSync(path.join(this.logDir, file))
}))
.sort((a, b) => b.stats.mtime - a.stats.mtime);
if (files.length > this.maxFiles) {
files.slice(this.maxFiles).forEach(file => {
fs.unlinkSync(file.path);
console.log(`Удалён старый лог-файл: ${file.name}`);
});
}
} catch (error) {
console.error('Ошибка при очистке логов:', error.message);
}
}
log(level, message, ...args) {
if (!this.shouldLog(level)) return;
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
const fullMessage = args.length > 0 ? `${logMessage} ${JSON.stringify(args)}` : logMessage;
const logFile = this.getLogFilePath(level);
// Проверяем размер файла и ротируем при необходимости
this.rotateLogFile(logFile);
// Записываем в файл
fs.appendFileSync(logFile, fullMessage + '\n', 'utf8');
// Дублируем в консоль
console.log(fullMessage);
}
error(message, ...args) { this.log('error', message, ...args); }
warn(message, ...args) { this.log('warn', message, ...args); }
info(message, ...args) { this.log('info', message, ...args); }
debug(message, ...args) { this.log('debug', message, ...args); }
}
// Использование
const logger = new Logger({
logDir: './logs',
maxFileSize: 1024 * 1024, // 1MB для демонстрации
maxFiles: 3,
level: 'debug'
});
logger.info('Сервер запущен');
logger.warn('Предупреждение о низком объёме места на диске');
logger.error('Ошибка подключения к базе данных', { host: 'localhost', port: 5432 });
logger.debug('Отладочная информация', { userId: 12345, action: 'login' });
Пример 3: Утилита для резервного копирования
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
class BackupManager {
constructor(options = {}) {
this.backupDir = options.backupDir || './backups';
this.maxBackups = options.maxBackups || 10;
this.compressionLevel = options.compressionLevel || 6;
this.ensureBackupDirectory();
}
ensureBackupDirectory() {
if (!fs.existsSync(this.backupDir)) {
fs.mkdirSync(this.backupDir, { recursive: true });
}
}
async backupDirectory(sourceDir, backupName) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupFileName = `${backupName}-${timestamp}.tar.gz`;
const backupPath = path.join(this.backupDir, backupFileName);
try {
console.log(`Создание резервной копии: ${sourceDir} -> ${backupPath}`);
// Проверяем существование исходной директории
if (!fs.existsSync(sourceDir)) {
throw new Error(`Исходная директория не найдена: ${sourceDir}`);
}
// Создаём архив
const tarCommand = `tar -czf "${backupPath}" -C "${path.dirname(sourceDir)}" "${path.basename(sourceDir)}"`;
execSync(tarCommand, { stdio: 'inherit' });
// Получаем информацию о созданном архиве
const stats = fs.statSync(backupPath);
const backupInfo = {
name: backupFileName,
path: backupPath,
size: stats.size,
created: stats.birthtime,
sourceDir: sourceDir
};
console.log(`Резервная копия создана: ${backupFileName} (${this.formatBytes(stats.size)})`);
// Очищаем старые резервные копии
this.cleanupOldBackups(backupName);
return backupInfo;
} catch (error) {
console.error('Ошибка при создании резервной копии:', error.message);
// Удаляем неполный архив в случае ошибки
if (fs.existsSync(backupPath)) {
fs.unlinkSync(backupPath);
}
throw error;
}
}
cleanupOldBackups(backupName) {
try {
const backupFiles = fs.readdirSync(this.backupDir)
.filter(file => file.startsWith(backupName) && file.endsWith('.tar.gz'))
.map(file => ({
name: file,
path: path.join(this.backupDir, file),
stats: fs.statSync(path.join(this.backupDir, file))
}))
.sort((a, b) => b.stats.birthtime - a.stats.birthtime);
if (backupFiles.length > this.maxBackups) {
const filesToDelete = backupFiles.slice(this.maxBackups);
filesToDelete.forEach(file => {
fs.unlinkSync(file.path);
console.log(`Удалена старая резервная копия: ${file.name}`);
});
}
} catch (error) {
console.error('Ошибка при очистке старых резервных копий:', error.message);
}
}
listBackups() {
try {
const backupFiles = fs.readdirSync(this.backupDir)
.filter(file => file.endsWith('.tar.gz'))
.map(file => {
const filePath = path.join(this.backupDir, file);
const stats = fs.statSync(filePath);
return {
name: file,
size: this.formatBytes(stats.size),
created: stats.birthtime.toISOString(),
path: filePath
};
})
.sort((a, b) => new Date(b.created) - new Date(a.created));
return backupFiles;
} catch (error) {
console.error('Ошибка при получении списка резервных копий:', error.message);
return [];
}
}
formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
async restoreBackup(backupName, targetDir) {
const backupPath = path.join(this.backupDir, backupName);
if (!fs.existsSync(backupPath)) {
throw new Error(`Резервная копия не найдена: ${backupPath}`);
}
try {
console.log(`Восстановление резервной копии: ${backupPath} -> ${targetDir}`);
// Создаём целевую директорию если не существует
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Извлекаем архив
const tarCommand = `tar -xzf "${backupPath}" -C "${targetDir}"`;
execSync(tarCommand, { stdio: 'inherit' });
console.log(`Резервная копия восстановлена в: ${targetDir}`);
} catch (error) {
console.error('Ошибка при восстановлении резервной копии:', error.message);
throw error;
}
}
}
// Использование
const backupManager = new BackupManager({
backupDir: './backups',
maxBackups: 5
});
// Создание резервной копии
backupManager.backupDirectory('./config', 'config-backup')
.then(backupInfo => {
console.log('Резервная копия создана:', backupInfo);
// Показываем список всех резервных копий
const backups = backupManager.listBackups();
console.log('\nСписок резервных копий:');
backups.forEach(backup => {
console.log(`- ${backup.name} (${backup.size}, ${backup.created})`);
});
})
.catch(error => {
console.error('Ошибка:', error.message);
});
Сравнение подходов: синхронные vs асинхронные операции
Аспект | Синхронные операции | Асинхронные операции |
---|---|---|
Блокировка потока | Блокируют выполнение кода | Не блокируют выполнение |
Производительность | Медленнее при обработке множества файлов | Быстрее при параллельной обработке |
Простота использования | Простой линейный код | Требует понимания callback/promises |
Обработка ошибок | try/catch блоки | callback errors или .catch() |
Использование в скриптах | Подходит для простых скриптов | Лучше для серверных приложений |
Потребление памяти | Загружает файл полностью | Может использовать потоки |
Работа с большими файлами: потоки (streams)
Для работы с большими файлами (более 100MB) рекомендуется использовать потоки, которые позволяют обрабатывать данные по частям:
const fs = require('fs');
const readline = require('readline');
// Чтение большого файла построчно
async function processLargeFile(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
let lineCount = 0;
const startTime = Date.now();
for await (const line of rl) {
lineCount++;
// Обрабатываем каждую строку
if (line.includes('ERROR')) {
console.log(`Ошибка в строке ${lineCount}: ${line}`);
}
// Показываем прогресс каждые 10000 строк
if (lineCount % 10000 === 0) {
const elapsed = Date.now() - startTime;
console.log(`Обработано ${lineCount} строк за ${elapsed}ms`);
}
}
console.log(`Обработка завершена. Всего строк: ${lineCount}`);
}
// Копирование больших файлов с использованием потоков
function copyLargeFile(source, destination) {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(destination);
let totalBytes = 0;
readStream.on('data', (chunk) => {
totalBytes += chunk.length;
console.log(`Скопировано: ${totalBytes} байт`);
});
readStream.on('error', reject);
writeStream.on('error', reject);
writeStream.on('finish', () => {
console.log(`Файл скопирован: ${totalBytes} байт`);
resolve();
});
readStream.pipe(writeStream);
});
}
// Пример использования
processLargeFile('./large-log.txt').catch(console.error);
copyLargeFile('./large-file.dat', './backup-large-file.dat').catch(console.error);
Интеграция с другими пакетами
Модуль fs отлично работает в связке с другими полезными пакетами:
// Установка: npm install chokidar glob archiver
const fs = require('fs');
const chokidar = require('chokidar'); // Мониторинг изменений файлов
const glob = require('glob'); // Поиск файлов по шаблону
const archiver = require('archiver'); // Создание архивов
// Мониторинг изменений в файлах
function watchFiles(pattern, callback) {
const watcher = chokidar.watch(pattern, {
persistent: true,
ignoreInitial: true
});
watcher.on('change', (path) => {
console.log(`Файл изменён: ${path}`);
callback('change', path);
});
watcher.on('add', (path) => {
console.log(`Файл добавлен: ${path}`);
callback('add', path);
});
watcher.on('unlink', (path) => {
console.log(`Файл удалён: ${path}`);
callback('unlink', path);
});
return watcher;
}
// Поиск файлов по шаблону
function findFiles(pattern) {
return new Promise((resolve, reject) => {
glob(pattern, (error, files) => {
if (error) {
reject(error);
return;
}
resolve(files);
});
});
}
// Создание zip-архива
function createZipArchive(files, outputPath) {
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(outputPath);
const archive = archiver('zip', { zlib: { level: 9 } });
output.on('close', () => {
console.log(`Архив создан: ${outputPath} (${archive.pointer()} байт)`);
resolve();
});
archive.on('error', reject);
archive.pipe(output);
files.forEach(file => {
archive.file(file, { name: file });
});
archive.finalize();
});
}
// Пример использования
async function autoBackupSystem() {
try {
// Находим все .json файлы
const configFiles = await findFiles('./config/**/*.json');
console.log('Найдено файлов конфигурации:', configFiles.length);
// Создаём архив
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const archivePath = `./backups/config-${timestamp}.zip`;
await createZipArchive(configFiles, archivePath);
// Настраиваем мониторинг
const watcher = watchFiles('./config/**/*.json', (event, path) => {
console.log(`Изменение конфигурации: ${event} - ${path}`);
// Здесь можно добавить логику автоматического бэкапа
});
console.log('Система автоматического резервного копирования запущена');
} catch (error) {
console.error('Ошибка в системе резервного копирования:', error.message);
}
}
autoBackupSystem();
Оптимизация производительности
Несколько советов для оптимизации работы с файлами:
- Используйте асинхронные операции в серверных приложениях для предотвращения блокировки Event Loop
- Применяйте потоки для обработки файлов размером более 100MB
- Кэшируйте результаты чтения для часто используемых файлов
- Пулы подключений для одновременной обработки множества файлов
- Сжатие данных для уменьшения размера файлов
const fs = require('fs');
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
// Пул воркеров для обработки файлов
class FileProcessingPool {
constructor(workerScript, poolSize = 4) {
this.workerScript = workerScript;
this.poolSize = poolSize;
this.workers = [];
this.queue = [];
this.activeJobs = 0;
this.initWorkers();
}
initWorkers() {
for (let i = 0; i < this.poolSize; i++) {
const worker = new Worker(this.workerScript);
worker.on('message', (result) => {
this.handleWorkerMessage(worker, result);
});
worker.on('error', (error) => {
console.error('Ошибка воркера:', error);
});
this.workers.push({ worker, busy: false });
}
}
handleWorkerMessage(worker, result) {
const workerInfo = this.workers.find(w => w.worker === worker);
workerInfo.busy = false;
this.activeJobs--;
if (result.callback) {
result.callback(result.error, result.data);
}
this.processQueue();
}
processFile(filePath, callback) {
this.queue.push({ filePath, callback });
this.processQueue();
}
processQueue() {
if (this.queue.length === 0) return;
const availableWorker = this.workers.find(w => !w.busy);
if (!availableWorker) return;
const job = this.queue.shift();
availableWorker.busy = true;
this.activeJobs++;
availableWorker.worker.postMessage({
filePath: job.filePath,
callback: job.callback
});
}
async shutdown() {
await Promise.all(this.workers.map(w => w.worker.terminate()));
}
}
// Кэширование результатов чтения файлов
class FileCache {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
}
get(filePath) {
if (this.cache.has(filePath)) {
const item = this.cache.get(filePath);
// Проверяем, не изменился ли файл
try {
const stats = fs.statSync(filePath);
if (stats.mtime <= item.mtime) {
return item.data;
}
} catch (error) {
// Файл был удалён
this.cache.delete(filePath);
return null;
}
}
return null;
}
set(filePath, data) {
if (this.cache.size >= this.maxSize) {
// Удаляем самый старый элемент
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
try {
const stats = fs.statSync(filePath);
this.cache.set(filePath, {
data,
mtime: stats.mtime
});
} catch (error) {
console.error('Ошибка при кэшировании файла:', error.message);
}
}
clear() {
this.cache.clear();
}
}
// Пример использования кэша
const fileCache = new FileCache(50);
async function readFileWithCache(filePath) {
// Проверяем кэш
let data = fileCache.get(filePath);
if (data) {
console.log(`Данные получены из кэша: ${filePath}`);
return data;
}
// Читаем файл
try {
data = await fs.promises.readFile(filePath, 'utf8');
fileCache.set(filePath, data);
console.log(`Файл прочитан и закэширован: ${filePath}`);
return data;
} catch (error) {
console.error('Ошибка при чтении файла:', error.message);
return null;
}
}
Безопасность при работе с файлами
Важные аспекты безопасности при работе с файловой системой:
const fs = require('fs');
const path = require('path');
// Безопасная проверка пути файла
function isSafePath(userPath, allowedDir) {
const resolvedPath = path.resolve(userPath);
const resolvedAllowedDir = path.resolve(allowedDir);
return resolvedPath.startsWith(resolvedAllowedDir);
}
// Безопасное чтение файла
function safeReadFile(userPath, allowedDir) {
if (!isSafePath(userPath, allowedDir)) {
throw new Error('Доступ к файлу запрещён: путь находится вне разрешённой директории');
}
const fullPath = path.resolve(userPath);
try {
const stats = fs.statSync(fullPath);
// Проверяем, что это действительно файл
if (!stats.isFile()) {
throw new Error('Указанный путь не является файлом');
}
// Проверяем размер файла
if (stats.size > 10 * 1024 * 1024) { // 10MB
throw new Error('Файл слишком большой для чтения');
}
return fs.readFileSync(fullPath, 'utf8');
} catch (error) {
throw new Error(`Ошибка при чтении файла: ${error.message}`);
}
}
// Безопасная запись файла
function safeWriteFile(userPath, data, allowedDir) {
if (!isSafePath(userPath, allowedDir)) {
throw new Error('Доступ к файлу запрещён: путь находится вне разрешённой директории');
}
// Проверяем размер данных
if (Buffer.byteLength(data, 'utf8') > 5 * 1024 * 1024) { // 5MB
throw new Error('Данные слишком большие для записи');
}
const fullPath = path.resolve(userPath);
const dir = path.dirname(fullPath);
// Создаём директорию если не существует
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
try {
fs.writeFileSync(fullPath, data, 'utf8');
return true;
} catch (error) {
throw new Error(`Ошибка при записи файла: ${error.message}`);
}
}
// Валидация имени файла
function isValidFileName(fileName) {
// Запрещённые символы в именах файлов
const invalidChars = /[<>:"/\\|?*\x00-\x1f]/;
// Запрещённые имена в Windows
const reservedNames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
return !invalidChars.test(fileName) &&
!reservedNames.test(fileName) &&
fileName.length > 0 &&
fileName.length <= 255;
}
// Пример безопасного файлового менеджера
class SecureFileManager {
constructor(baseDir) {
this.baseDir = path.resolve(baseDir);
this.ensureBaseDirectory();
}
ensureBaseDirectory() {
if (!fs.existsSync(this.baseDir)) {
fs.mkdirSync(this.baseDir, { recursive: true });
}
}
readFile(relativePath) {
const fullPath = path.join(this.baseDir, relativePath);
return safeReadFile(fullPath, this.baseDir);
}
writeFile(relativePath, data) {
const fileName = path.basename(relativePath);
if (!isValidFileName(fileName)) {
throw new Error('Недопустимое имя файла');
}
const fullPath = path.join(this.baseDir, relativePath);
return safeWriteFile(fullPath, data, this.baseDir);
}
deleteFile(relativePath) {
const fullPath = path.join(this.baseDir, relativePath);
if (!isSafePath(fullPath, this.baseDir)) {
throw new Error('Доступ к файлу запрещён');
}
try {
fs.unlinkSync(fullPath);
return true;
} catch (error) {
throw new Error(`Ошибка при удалении файла: ${error.message}`);
}
}
listFiles() {
try {
return fs.readdirSync(this.baseDir, { withFileTypes: true })
.filter(dirent => dirent.isFile())
.map(dirent => ({
name: dirent.name,
size: fs.statSync(path.join(this.baseDir, dirent.name)).size,
modified: fs.statSync(path.join(this.baseDir, dirent.name)).mtime
}));
} catch (error) {
throw new Error(`Ошибка при получении списка файлов: ${error.message}`);
}
}
}
// Использование
const fileManager = new SecureFileManager('./secure-files');
console.log('Файловый менеджер инициализирован');
Современные альтернативы и дополнения
Хотя встроенный модуль fs покрывает большинство потребностей, существуют полезные альтернативы:
- fs-extra – расширенная версия fs с дополнительными методами
- graceful-fs – улучшенная обработка ошибок файловой системы
- chokidar – кроссплатформенный мониторинг файлов
- glob – поиск файлов по шаблонам
- mkdirp – рекурсивное создание директорий
- rimraf – рекурсивное удаление файлов и директорий
Для современных проектов на VPS серверах рекомендуется использовать fs-extra:
// npm install fs-extra
const fs = require('fs-extra');
// Копирование директорий
await fs.copy('./source', './destination');
// Перемещение файлов
await fs.move('./old-location', './new-location');
// Очистка директории
await fs.emptyDir('./temp');
// Проверка существования
if (await fs.pathExists('./some-file.txt')) {
console.log('Файл существует');
}
// JSON операции
const data = await fs.readJson('./config.json');
await fs.writeJson('./config.json', { ...data, updated: true });
Автоматизация и скрипты
Модуль fs открывает широкие возможности для автоматизации серверных задач:
- Автоматическое развёртывание – чтение конфигураций, копирование файлов
- Мониторинг системы – анализ лог-файлов, создание отчётов
- Обслуживание сервера – ротация логов, очистка временных файлов
- Резервное копирование – автоматическое создание и управление бэкапами
- Обработка данных – парсинг CSV, JSON, XML файлов
Для высоконагруженных приложений на выделенных серверах особенно важна эффективная работа с файловой системой.
Заключение и рекомендации
Модуль fs является фундамент
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.