Node.js و Express

وحدة نظام الملفات (fs)

18 دقيقة الدرس 5 من 40

مقدمة إلى وحدة نظام الملفات

وحدة fs (File System) هي واحدة من أهم الوحدات الأساسية في Node.js. توفر واجهة برمجية للتفاعل مع نظام الملفات، مما يسمح لك بقراءة وكتابة وحذف ومعالجة الملفات والمجلدات.

مهم: توفر وحدة fs طرقًا متزامنة وغير متزامنة. استخدم الطرق غير المتزامنة في الإنتاج لتجنب حظر حلقة الأحداث.

استيراد وحدة fs

// واجهة برمجية قائمة على رد النداء (تقليدية) const fs = require('fs'); // واجهة برمجية قائمة على الوعود (حديثة، موصى بها) const fsPromises = require('fs').promises; // أو باستخدام التفكيك في ES6 const { readFile, writeFile } = require('fs').promises;

قراءة الملفات

توفر Node.js طرقًا متعددة لقراءة الملفات حسب احتياجاتك:

قراءة الملفات غير المتزامنة (رد النداء)

const fs = require('fs'); // قراءة الملف بشكل غير متزامن مع رد النداء fs.readFile('data.txt', 'utf8', (error, data) => { if (error) { console.error('Error reading file:', error); return; } console.log('File contents:', data); }); // بدون ترميز - يعيد Buffer fs.readFile('image.png', (error, data) => { if (error) { console.error('Error:', error); return; } console.log('File size:', data.length, 'bytes'); console.log('Buffer:', data); // <Buffer 89 50 4e 47...> });

قراءة الملفات المتزامنة

const fs = require('fs'); try { // يحظر التنفيذ حتى تتم قراءة الملف const data = fs.readFileSync('data.txt', 'utf8'); console.log('File contents:', data); } catch (error) { console.error('Error reading file:', error); }
تحذير: تجنب readFileSync() في خوادم الإنتاج لأنها تحظر حلقة الأحداث، مما يمنع تشغيل العمليات الأخرى.

قراءة الملفات القائمة على الوعود (موصى بها)

const fs = require('fs').promises; // استخدام الوعود مع .then() fs.readFile('data.txt', 'utf8') .then(data => { console.log('File contents:', data); }) .catch(error => { console.error('Error:', error); }); // استخدام async/await (أنظف) async function readData() { try { const data = await fs.readFile('data.txt', 'utf8'); console.log('File contents:', data); return data; } catch (error) { console.error('Error reading file:', error); throw error; } } readData();

كتابة الملفات

الكتابة إلى الملفات بنفس أهمية القراءة. توفر Node.js طرقًا لإنشاء أو استبدال الملفات:

كتابة الملفات غير المتزامنة

const fs = require('fs'); const content = 'Hello from Node.js!'; // كتابة الملف (ينشئه إذا لم يكن موجودًا، يستبدله إذا كان موجودًا) fs.writeFile('output.txt', content, 'utf8', (error) => { if (error) { console.error('Error writing file:', error); return; } console.log('File written successfully'); }); // كتابة بيانات JSON const jsonData = { name: 'John Doe', age: 30, email: 'john@example.com' }; fs.writeFile('user.json', JSON.stringify(jsonData, null, 2), 'utf8', (error) => { if (error) { console.error('Error:', error); return; } console.log('JSON file saved'); });

كتابة الملفات القائمة على الوعود

const fs = require('fs').promises; async function saveData() { try { const data = 'Important data to save'; await fs.writeFile('important.txt', data, 'utf8'); console.log('File saved successfully'); // كتابة JSON const user = { name: 'Jane', age: 25 }; await fs.writeFile('user.json', JSON.stringify(user, null, 2)); console.log('JSON saved'); } catch (error) { console.error('Error saving files:', error); } } saveData();

الإضافة إلى الملفات

لإضافة محتوى إلى نهاية ملف موجود دون استبداله:

const fs = require('fs').promises; async function appendLog() { try { const timestamp = new Date().toISOString(); const logEntry = `[${timestamp}] Application started\n`; await fs.appendFile('app.log', logEntry, 'utf8'); console.log('Log entry added'); } catch (error) { console.error('Error appending to file:', error); } } appendLog(); // إضافات متعددة async function createLog() { try { await fs.appendFile('events.log', 'Event 1\n'); await fs.appendFile('events.log', 'Event 2\n'); await fs.appendFile('events.log', 'Event 3\n'); console.log('All events logged'); } catch (error) { console.error('Error:', error); } }

التحقق من وجود الملف

const fs = require('fs').promises; async function checkFile(filename) { try { // fs.access يرمي خطأ إذا لم يكن الملف موجودًا await fs.access(filename); console.log(`${filename} exists`); return true; } catch (error) { console.log(`${filename} does not exist`); return false; } } checkFile('data.txt'); // التحقق مما إذا كان الملف قابلاً للقراءة، الكتابة async function checkPermissions(filename) { try { // التحقق من إذن القراءة await fs.access(filename, fs.constants.R_OK); console.log('File is readable'); // التحقق من إذن الكتابة await fs.access(filename, fs.constants.W_OK); console.log('File is writable'); } catch (error) { console.error('Permission denied:', error.message); } }

حذف الملفات

const fs = require('fs').promises; async function deleteFile(filename) { try { await fs.unlink(filename); console.log(`${filename} deleted successfully`); } catch (error) { if (error.code === 'ENOENT') { console.error('File does not exist'); } else { console.error('Error deleting file:', error); } } } deleteFile('old-file.txt'); // حذف ملفات متعددة async function deleteMultiple(files) { try { const deletePromises = files.map(file => fs.unlink(file)); await Promise.all(deletePromises); console.log('All files deleted'); } catch (error) { console.error('Error deleting files:', error); } } deleteMultiple(['temp1.txt', 'temp2.txt', 'temp3.txt']);

إعادة تسمية ونقل الملفات

const fs = require('fs').promises; async function renameFile(oldPath, newPath) { try { await fs.rename(oldPath, newPath); console.log(`Renamed ${oldPath} to ${newPath}`); } catch (error) { console.error('Error renaming file:', error); } } // إعادة تسمية الملف renameFile('old-name.txt', 'new-name.txt'); // نقل الملف إلى مجلد مختلف renameFile('data.txt', 'backup/data.txt'); // نقل وإعادة تسمية في نفس الوقت async function moveAndRename() { try { await fs.rename('downloads/report.pdf', 'documents/monthly-report-2024.pdf'); console.log('File moved and renamed'); } catch (error) { console.error('Error:', error); } }

الحصول على معلومات الملف

const fs = require('fs').promises; async function getFileInfo(filename) { try { const stats = await fs.stat(filename); console.log('File Stats:'); console.log(' Size:', stats.size, 'bytes'); console.log(' Created:', stats.birthtime); console.log(' Modified:', stats.mtime); console.log(' Is File:', stats.isFile()); console.log(' Is Directory:', stats.isDirectory()); // حجم الملف بتنسيق قابل للقراءة const sizeInKB = (stats.size / 1024).toFixed(2); const sizeInMB = (stats.size / (1024 * 1024)).toFixed(2); console.log(` Size: ${sizeInKB} KB (${sizeInMB} MB)`); } catch (error) { console.error('Error getting file info:', error); } } getFileInfo('large-file.zip');

العمل مع المجلدات

توفر وحدة fs أيضًا طرقًا لإنشاء وقراءة وإزالة المجلدات:

إنشاء المجلدات

const fs = require('fs').promises; // إنشاء مجلد واحد async function createDir() { try { await fs.mkdir('uploads'); console.log('Directory created'); } catch (error) { if (error.code === 'EEXIST') { console.log('Directory already exists'); } else { console.error('Error:', error); } } } // إنشاء مجلدات متداخلة (تكرارية) async function createNestedDirs() { try { await fs.mkdir('data/users/profiles', { recursive: true }); console.log('Nested directories created'); } catch (error) { console.error('Error:', error); } } createNestedDirs();

قراءة المجلدات

const fs = require('fs').promises; const path = require('path'); // قائمة جميع الملفات في المجلد async function listFiles(directory) { try { const files = await fs.readdir(directory); console.log(`Files in ${directory}:`); files.forEach(file => { console.log(' -', file); }); } catch (error) { console.error('Error reading directory:', error); } } listFiles('./'); // الحصول على معلومات تفصيلية عن كل ملف async function listFilesDetailed(directory) { try { const files = await fs.readdir(directory); for (const file of files) { const filePath = path.join(directory, file); const stats = await fs.stat(filePath); console.log(`${file}:`); console.log(` Type: ${stats.isFile() ? 'File' : 'Directory'}`); console.log(` Size: ${stats.size} bytes`); console.log(` Modified: ${stats.mtime}`); } } catch (error) { console.error('Error:', error); } }

حذف المجلدات

const fs = require('fs').promises; // إزالة مجلد فارغ async function removeDir(dirPath) { try { await fs.rmdir(dirPath); console.log(`Directory ${dirPath} removed`); } catch (error) { if (error.code === 'ENOTEMPTY') { console.error('Directory is not empty'); } else { console.error('Error:', error); } } } // إزالة المجلد وجميع محتوياته (تكرارية) async function removeDirRecursive(dirPath) { try { await fs.rm(dirPath, { recursive: true, force: true }); console.log(`Directory ${dirPath} and contents removed`); } catch (error) { console.error('Error:', error); } } removeDirRecursive('temp-folder');

مراقبة الملفات والمجلدات

راقب الملفات والمجلدات للتغييرات في الوقت الفعلي:

const fs = require('fs'); // مراقبة ملف للتغييرات const watcher = fs.watch('config.json', (eventType, filename) => { console.log(`Event: ${eventType}`); console.log(`File: ${filename}`); if (eventType === 'change') { console.log('File was modified'); } else if (eventType === 'rename') { console.log('File was renamed or deleted'); } }); // إيقاف المراقبة بعد 60 ثانية setTimeout(() => { watcher.close(); console.log('Stopped watching file'); }, 60000); // مراقبة مجلد fs.watch('./data', { recursive: true }, (eventType, filename) => { console.log(`Change detected in: ${filename}`); });

نسخ الملفات

const fs = require('fs').promises; async function copyFile(source, destination) { try { await fs.copyFile(source, destination); console.log(`Copied ${source} to ${destination}`); } catch (error) { console.error('Error copying file:', error); } } copyFile('original.txt', 'backup/original-copy.txt'); // النسخ مع خيارات async function copyWithOptions(source, destination) { try { // COPYFILE_EXCL: فشل إذا كانت الوجهة موجودة await fs.copyFile(source, destination, fs.constants.COPYFILE_EXCL); console.log('File copied (destination didn\'t exist)'); } catch (error) { if (error.code === 'EEXIST') { console.error('Destination file already exists'); } else { console.error('Error:', error); } } }

مثال عملي: مدير الملفات

const fs = require('fs').promises; const path = require('path'); class FileManager { async readJSON(filename) { const data = await fs.readFile(filename, 'utf8'); return JSON.parse(data); } async writeJSON(filename, data) { const json = JSON.stringify(data, null, 2); await fs.writeFile(filename, json, 'utf8'); } async copyDirectory(source, destination) { await fs.mkdir(destination, { recursive: true }); const files = await fs.readdir(source); for (const file of files) { const sourcePath = path.join(source, file); const destPath = path.join(destination, file); const stats = await fs.stat(sourcePath); if (stats.isDirectory()) { await this.copyDirectory(sourcePath, destPath); } else { await fs.copyFile(sourcePath, destPath); } } } async getDirectorySize(dirPath) { let totalSize = 0; const files = await fs.readdir(dirPath); for (const file of files) { const filePath = path.join(dirPath, file); const stats = await fs.stat(filePath); if (stats.isFile()) { totalSize += stats.size; } else if (stats.isDirectory()) { totalSize += await this.getDirectorySize(filePath); } } return totalSize; } } // الاستخدام const fm = new FileManager(); fm.getDirectorySize('./data').then(size => { console.log(`Total size: ${(size / 1024 / 1024).toFixed(2)} MB`); });

تمرين تطبيقي

  1. أنشئ برنامجًا يقرأ ملف نصي ويحسب عدد الكلمات
  2. اكتب دالة تنشئ نسخة احتياطية من ملف مع طابع زمني
  3. نفّذ مدير ملف سجل يضيف إدخالات مع طوابع زمنية
  4. أنشئ دالة تسرد جميع الملفات في مجلد ومجلداته الفرعية
  5. اكتب برنامجًا يراقب ملف تكوين ويعيد تحميل الإعدادات عندما يتغير
  6. ابنِ منظم ملفات بسيط ينقل الملفات إلى مجلدات بناءً على امتداداتها

ملخص

في هذا الدرس، تعلمت:

  • كيفية قراءة الملفات باستخدام ردود النداء والوعود و async/await
  • الكتابة والإضافة إلى الملفات
  • التحقق من وجود الملف والأذونات
  • حذف وإعادة تسمية ونسخ الملفات
  • الحصول على معلومات الملف باستخدام fs.stat()
  • إنشاء وقراءة وإزالة المجلدات
  • مراقبة الملفات والمجلدات للتغييرات
  • الفرق بين طرق fs المتزامنة وغير المتزامنة
  • أفضل ممارسات عمليات نظام الملفات في Node.js