اذهب إلى المحتوى

التعامل مع الملفات باستخدام الوحدة fs في Node.js


Hassan Hedr

كثيرًا ما نحتاج للتعامل مع نظام الملفات، فمثلًا لتخزين بعض الملفات بعد تنزيلها، أو لترتيب بعض البيانات ضمن مجلدات أو لقراءة الملفات للتعامل مع محتوياتها ضمن بعض التطبيقات، وحتى تطبيقات النظم الخلفية أو أدوات واجهة سطر الأوامر CLI قد تحتاج أحيانًا لحفظ بعض البيانات إلى ملفات، والتطبيقات التي تتعامل مع البيانات تحتاج أحيانًا لتصديرها بمختلف الصيغ مثل JSON أو CSV أو ملفات برنامج إكسل، فكل تلك المتطلبات تحتاج للتعامل مع نظام الملفات ضمن نظام التشغيل التي تعمل عليه.

توفر نود طريقة برمجية للتعامل مع الملفات باستخدام الوحدة البرمجية ‎fs‎، وهي اختصار لجملة "نظام الملفات" أو "file system" حيث تحتوي على العديد من التوابع التي نحتاجها لقراءة الملفات أو الكتابة إليها أو حذفها، وهذه المزايا تجعل من لغة جافاسكربت لغة مفيدة لاستخدامها ضمن تطبيقات النظم الخلفية و أدوات سطر الأوامر.

سنتعرف في هذا المقال على الوحدة البرمجية ‎fs‎ وسنستخدمها لقراءة الملفات وإنشاء ملفات جديدة والكتابة إليها وحذف الملفات وحتى نقل الملفات من مجلد إلى آخر، حيث توفر الوحدة البرمجية ‎fs‎ توابع للتعامل مع الملفات بالطريقتين المتزامنة synchronously واللامتزامنة asynchronously وباستخدام مجاري البيانات streams، حيث سنستخدم في هذا المقال الطريقة اللامتزامنة باستخدام الوعود Promises وهي الطريقة الأكثر استخدامًا.

المستلزمات

قراءة الملفات باستخدام ‎readFile()‎

سنطور في هذه الفقرة برنامجًا في نود لقراءة الملفات، وسنستعين بالتابع ‎readFile()‎ الذي توفره الوحدة البرمجية ‎fs‎ في نود لقراءة محتوى ملف معين وتخزينه ضمن متغير ثم طباعته إلى الطرفية.

سنبدأ بإعداد المجلد الذي سيحوي على ملفات الأمثلة المستخدمة في هذا المقال ونُنشئ لذلك مجلدًا جديدًا بالاسم ‎node-files‎ كالتالي:

$ mkdir node-files

وندخل لذلك المجلد باستخدام الأمر ‎cd‎:

$ cd node-files

ننشئ داخل المجلد ملفين الأول هو الملف الذي سنحاول قراءته باستخدام البرنامج، والثاني هو ملف جافاسكربت للبرنامج الذي سنطوره، ونبدأ بإنشاء ملف جديد يحوي المحتوى النصي ‎greetings.txt‎، وهنا سنُنشئ الملف عن طريق سطر الأوامر كالتالي:

$ echo "hello, hola, bonjour, hallo" > greetings.txt

في الأمر السابق سيطبع الأمر ‎echo‎ النص المُمرر له إلى الطرفية، واستخدمنا المعامل ‎>‎ لإعادة توجيه خرج الأمر ‎echo‎ إلى الملف النصي الجديد ‎greetings.txt‎.

والآن ننشئ ملف جافاسكربت جديد للبرنامج بالاسم ‎readFile.js‎ ونفتحه باستخدام أي محرر نصوص، حيث سنستخدم ضمن أمثلتنا محرر النصوص ‎nano‎ كالتالي:

$ nano readFile.js

يتكون البرنامج الذي سنكتبه من ثلاث أقسام رئيسية، حيث نبدأ أولًا باستيراد الوحدة البرمجية التي تحوي توابع التعامل مع الملفات كالتالي:

const fs = require('fs').promises;

تحوي الوحدة ‎fs‎ كافة التوابع المستخدمة في التعامل مع نظام الملفات، ونلاحظ كيف استوردنا منها الجزء ‎.promises‎ حيث كانت طريقة كتابة الشيفرة اللامتزامنة سابقًا ضمن الوحدة ‎fs‎ عبر استخدام دوال رد النداء callbacks، ولاحقًا وبعد أن انتشر استخدام الوعود كطريقة بديلة أضاف فريق التطوير في نود دعمًا لها ضمن الوحدة ‎fs‎، حيث وبدءًا من الإصدار رقم 10 من نود أضيفت الخاصية ‎promises‎ ضمن كائن الوحدة البرمجية ‎fs‎ والتي تحوي التوابع التي تدعم طريقة الوعود، بينما بقي عمل الوحدة البرمجية ‎fs‎ الأساسية كما هي سابقًا باستخدام توابع رد النداء لدعم البرامج التي تستخدم الطريقة القديمة، وفي أمثلتنا سنستخدم نسخة التوابع التي تعتمد على الوعود.

في القسم الثاني من البرنامج سنضيف دالة لامتزامنة لقراءة محتوى الملف، حيث يمكن تعريف الدوال اللامتزامنة في جافاسكربت بإضافة الكلمة ‎async‎ في بدايتها، وبذلك نستطيع ضمن التابع انتظار نتيجة كائنات الوعود باستخدام الكلمة ‎await‎ مباشرةً بدلًا من ربط العمليات المتتالية باستخدام التابع ‎.then()‎.

والآن نعرف الدالة ‎readFile()‎ التي تقبل سلسلة نصية ‎filePath‎ تمثِّل مسار الملف الذي نود قراءته، حيث سنستعين بتوابع الوحدة ‎fs‎ لقراءة محتوى الملف المطلوب وتخزينه ضمن متغير باستخدام صيفة ‎async/await‎ كالتالي:

const fs = require('fs').promises;

async function readFile(filePath) {
  try {
    const data = await fs.readFile(filePath);
    console.log(data.toString());
  } catch (error) {
    console.error(`‎Got an error trying to read the file: ${error.message}‎`);
  }
}

يمكن التقاط الأخطاء التي قد يرميها استدعاء التابع ‎fs.readFile()‎ باستخدام ‎try...catch‎، حيث نستدعي التابع ‎fs.readFile()‎ ضمن جسم ‎try‎ ثم نخزن النتيجة ضمن المتغير ‎data‎، ويقبل ذلك التابع معاملًا وحيدًا إجباريًا وهو مسار الملف الذي نود قراءته، ويعيد كائن مخزن مؤقت ‎buffer‎ كنتيجة لعملية القراءة حيث يمكن لهذا الكائن أن يحوي أي نوع من الملفات، ولكي نطبع ذلك المحتوى إلى الطرفية يجب تحويله إلى سلسلة نصية باستخدام التابع ‎toString()‎ من كائن المخزن المؤقت.

وفي حال رمي خطأ ما فالسبب يكون إما لعدم وجود الملف الذي نريد قراءته، أو لأن المستخدم لا يملك إذنًا لقراءته، ففي هذه الحالة سنطبع رسالة خطأ إلى الطرفية.

أما القسم الثالث والأخير من البرنامج هو استدعاء دالة قراءة الملف مع تمرير اسم الملف ‎greetings.txt‎ ليصبح البرنامج كالتالي:

const fs = require('fs').promises;

async function readFile(filePath) {
  try {
    const data = await fs.readFile(filePath);
    console.log(data.toString());
  } catch (error) {
    console.error(`Got an error trying to read the file: ${error.message}`);
  }
}

readFile('greetings.txt');

نحفظ الملف ونخرج منه وفي حال كنت تستخدم أيضًا محرر النصوص nano يمكنك الخروج بالضغط على الاختصار ‎CTRL+X‎، وعند تنفيذ البرنامج سيقرأ المحتوى النصي للملف ‎greetings.txt‎ ويطبع محتواه إلى الطرفية، والآن ننفذ البرنامج عبر الأمر ‎node‎ لنرى النتيجة:

$ node readFile.js

بعد تنفيذ الأمر سيظهر الخرج التالي:

hello, hola, bonjour, hallo

وبذلك نكون قد استخدمنا التابع ‎readFile()‎ من الوحدة ‎fs‎ لقراءة محتوى الملف باستخدام صيغة ‎async/await‎.

ملاحظة: إذا كنت تستخدم إصدارًا قديمًا من نود وحاولت استخدام الوحدة ‎fs‎ بالطريقة السابقة سيظهر لك رسالة التحذير التالية:

(node:13085) ExperimentalWarning: The fs.promises API is experimental

حيث أن الخاصية ‎promises‎ من الوحدة ‎fs‎ تم اعتمادها رسميًا منذ الإصدار 10 من نود، وقبل ذلك كانت في المرحلة التجريبية وهذا سبب رسالة التحذير السابقة، ولاحقًا وتحديدًا ضمن إصدار نود رقم 12.6 أصبحت التوابع ضمن تلك الخاصية مستقرة وأزيلت رسالة التحذير تلك.

الآن وبعد أن تعرفنا على طريقة قراءة الملفات باستخدام الوحدة ‎fs‎ سنتعلم في الفقرة التالية طريقة إنشاء الملفات الجديدة وكتابة المحتوى النصي إليها.

كتابة الملفات باستخدام ‎writeFile()‎

سنتعلم في هذه الفقرة طريقة كتابة الملفات باستخدام التابع ‎writeFile()‎ من الوحدة البرمجية ‎fs‎، وذلك بكتابة ملف بصيغة CSV يحـوي على بيانات لفاتورة شراء، حيث سنبدأ بإنشاء ملف جديد وإضافة ترويسات عناوين الأعمدة له، ثم سنتعلم طريقة إضافة بيانات جديدة إلى نهاية الملف.

نبدأ أولًا بإنشاء ملف جافاسكربت جديد للبرنامج ونفتحه باستخدام محرر النصوص كالتالي:

$ nano writeFile.js

ونستورد الوحدة ‎fs‎ كالتالي:

const fs = require('fs').promises;

وسنستخدم في هذا المثال أيضًا صيغة ‎async/await‎ لتعريف دالتين، الأولى لإنشاء ملف CSV جديد والثانية لكتابة بيانات جديدة إليه.

نفتح الملف ضمن محرر النصوص ونضيف الدالة التالي:

const fs = require('fs').promises;

async function openFile() {
  try {
    const csvHeaders = 'name,quantity,price'
    await fs.writeFile('groceries.csv', csvHeaders);
  } catch (error) {
    console.error(`Got an error trying to write to a file: ${error.message}`);
  }
}

نُعرّف المتغير ‎csvHeaders‎ والذي يحتوي على عناوين رؤوس الأعمدة لملف CSV، ثم نستدعي التابع ‎writeFile()‎ من وحدة ‎fs‎ لإنشاء ملف جديد وكتابة البيانات إليه، حيث أن المعامل الأول المُمرر له هو مسار الملف الجديد، وإذا مررنا اسم الملف فقط فسيُنشَأ الملف الجديد ضمن المسار الحالي لتنفيذ البرنامج، وأما المعامل الثاني المُمرر هو البيانات التي نريد كتابتها ضمن الملف، وفي حالتنا هي عناوين الأعمدة الموجودة ضمن المتغير ‎csvHeaders‎.

والآن نضيف الدالة الثانية ومهمتها إضافة بيانات جديدة ضمن ملف الفاتورة كالتالي:

const fs = require('fs').promises;

async function openFile() {
  try {
    const csvHeaders = 'name,quantity,price'
    await fs.writeFile('groceries.csv', csvHeaders);
  } catch (error) {
    console.error(`Got an error trying to write to a file: ${error.message}`);
  }
}

async function addGroceryItem(name, quantity, price) {
  try {
    const csvLine = `\n${name},${quantity},${price}`
    await fs.writeFile('groceries.csv', csvLine, { flag: 'a' });
  } catch (error) {
    console.error(`Got an error trying to write to a file: ${error.message}`);
  }
}

عرفنا الدالة اللامتزامنة ‎addGroceryItem()‎ التي تقبل ثلاثة مُعاملات، وهي اسم المنتج والكمية والسعر للقطعة الواحدة منه، ويتم إنشاء السطر الجديد الذي نود كتابته إلى الملف باستخدام قالب نص template literal وتخزينه ضمن المتغير ‎csvLine‎، ثم نستدعي التابع ‎writeFile()‎ كما فعلنا سابقًا ضمن التابع الأول ‎openFile()‎، ولكن هذه المرة سنمرر كائن جافاسكربت كمعامل ثالث يحتوي على المفتاح ‎flag‎ بالقيمة ‎a‎، وتعبر تلك القيمة عن الرايات المستخدمة للتعامل مع نظام الملفات، والراية ‎a‎ هنا تخبر نود بأننا نريد إضافة ذلك المحتوى إلى الملف، وليس إعادة كتابة محتوى الملف كاملًا، وفي حال لم نمرر أي راية عند كتابة الملف كما فعلنا ضمن الدالة الأولى فإن القيمة الافتراضية هي الراية ‎w‎ والتي تعني إنشاء ملف جديد في حال لم يكن الملف موجودًا، وإذا كان موجودًا سيتم تبديله وإعادة كتابة محتواه كاملًا، ويمكنك الرجوع إلى التوثيق الرسمي لتلك الرايات من نود للتعرف عليها أكثر.

والآن لننهي كتابة البرنامج باستدعاء الدوال التي عرّفناها كالتالي:

...
async function addGroceryItem(name, quantity, price) {
  try {
    const csvLine = `\n${name},${quantity},${price}`
    await fs.writeFile('groceries.csv', csvLine, { flag: 'a' });
  } catch (error) {
    console.error(`Got an error trying to write to a file: ${error.message}`);
  }
}

(async function () {
  await openFile();
  await addGroceryItem('eggs', 12, 1.50);
  await addGroceryItem('nutella', 1, 4);
})();

وبما أن الدوال التي سنستدعيها لامتزامنة، فيمكننا تغليفها بدالة لامتزامنة واستدعاءها مباشرة كي نستطيع استخدام ‎await‎ لانتظار إكمال تنفيذها، وذلك لأنه لا يمكن ضمن إصدار نود الذي نستخدمه حاليًا استخدام await مباشرة ضمن النطاق العام global scope، بل حصرًا ضمن دوال لامتزامنة تُستخدم ضمن تعريفها الكلمة async، ولا حاجة لتسمية تلك الدالة ويمكننا تعريفها كدالة مجهولة لأن الغرض منها فقط التغليف والتنفيذ المباشر ولن نشير إليها من أي مكان آخر.

وبما أن كلا الدالتين ‎openFile()‎ و ‎addGroceryItem()‎ لا متزامنين فبدون انتظار نتيجة استدعاء الدالة الأولى ثم استدعاء الثانية لا يمكن ضمان ترتيب التنفيذ وبالتالي ترتيب المحتوى ضمن الملف الذي نريد إنشاءه، لذلك عرفنا دالة التغليف تلك الغير متزامنة بين قوسين وأضفنا قوسي الاستدعاء في النهاية قبل الفاصلة المنقوطة كي لاستدعائها مباشرةً، وتُدعى تلك الصيغة بصيغة التنفيذ المباشر لدالة Immediately-Invoked Function Expression أو IIFE، وباستخدام تلك الصيغة في مثالنا نضمن احتواء ملف CSV الجديد على الترويسات بدايةً ثم أول سطر للمنتج ‎eggs‎ وبعده المنتج الثاني ‎nutella‎.

والآن نحفظ الملف ونخرج منه ثم ننفذ البرنامج باستخدام الأمر ‎node‎:

$ node writeFile.js

لن نلاحظ أي خرج من التنفيذ ولكن سنلاحظ إنشاء ملف جديد ضمن المجلد الحالي ويمكن معاينة محتوى الملف ‎groceries.csv‎ باستخدام الأمر ‎cat‎ كالتالي:

$ cat groceries.csv

ليظهر الخرج التالي:

name,quantity,price
eggs,12,1.5
nutella,1,4

أنشأت الدالة ‎openFile()‎ ملف CSV وأضافت الترويسات له، ثم أضافت استدعاءات الدالة ‎addGroceryItem()‎ التي تليها سطرين من البيانات إلى ذلك الملف، وبذلك نكون قد تعلمنا طريقة استخدام التابع ‎writeFile()‎ لإنشاء الملفات الجديدة والتعديل على محتواها.

سنتعلم في الفقرة التالية كيف يمكننا حذف الملفات في حال أردنا إنشاء ملفات مؤقتة مثلًا، أو لإزالة بعض الملفات لتوفير مساحة التخزين على الجهاز.

حذف الملفات باستخدام ‎unlink()‎

سنتعلم في هذه الفقرة طريقة حذف الملفات باستخدام التابع ‎unlink()‎ من الوحدة البرمجية ‎fs‎، حيث سنكتب برنامجًا لحذف الملف ‎groceries.csv‎ الذي أنشأناه في الفقرة السابقة.

نبدأ بإنشاء ملف جافاسكربت جديد بالاسم deleteFile.js نُعرف ضمنه الدالة اللامتزامنة ‎deleteFile()‎ التي تقبل مسار الملف المراد حذفه، وبدورها ستمرر ذلك المعامل إلى التابع ‎unlink()‎ والذي سيحذف ذلك الملف من نظام الملفات كالتالي:

const fs = require('fs').promises;

async function deleteFile(filePath) {
  try {
    await fs.unlink(filePath);
    console.log(`Deleted ${filePath}`);
  } catch (error) {
    console.error(`Got an error trying to delete the file: ${error.message}`);
  }
}

deleteFile('groceries.csv');

تحذير: لن تُنقل الملفات المحذوفة باستخدام التابع ‎unlink()‎ إلى سلة المحذوفات بل ستُحذف نهائيًا من نظام الملفات، لذا تلك العملية لا يمكن الرجوع عنها ويجب الحذر والتأكد من الملفات التي نحاول حذفها قبل تنفيذ البرنامج.

والآن نخرج من الملف وننفذه كالتالي:

$ node deleteFile.js

ليظهر الخرج التالي:

Deleted groceries.csv

نستعرض الملفات الموجودة حاليًا بعد التنفيذ للتأكد من نجاح عملية الحذف باستخدام الأمر ‎ls‎ كالتالي:

$ ls

ليظهر لنا الملفات التالية:

deleteFile.js   greetings.txt   readFile.js     writeFile.js

نلاحظ حذف الملف بنجاح باستخدام التابع ‎unlink()‎، وبذلك نكون قد تعلمنا طريقة قراءة وكتابة وحذف الملفات، وسنتعلم في الفقرة التالية كيف يمكن نقل الملفات من مجلد لآخر، لنكون بذلك قد تعلمنا كافة العمليات التي تسمح بإدارة الملفات عن طريق نود.

نقل الملفات باستخدام ‎rename()‎

تُستخدم المجلدات لتنظيم وترتيب الملفات معًا، لذا من المفيد تعلم طريقة نقل تلك الملفات برمجيًا، حيث يتم ذلك في نود باستخدام التابع ‎rename()‎ من الوحدة ‎fs‎، وسنتعلم طريقة استخدامه بنقل الملف السابق ‎greetings.txt‎ إلى مجلد جديد مع إعادة تسميته.

نبدأ بإنشاء ذلك مجلد جديد بالاسم ‎test-data‎ ضمن المجلد الحالي كالتالي:

$ mkdir test-data

ونُنشئ نسخة عن الملف ‎greetings.txt‎ بتنفيذ أمر النسخ ‎cp‎ كالتالي:

$ cp greetings.txt greetings-2.txt

ثم نُنشئ ملف جافاسكربت للبرنامج كالتالي:

$ nano moveFile.js

ونُعرف ضمنه الدالة ‎moveFile()‎ لنقل الملف، والتي ستستدعي بدورها التابع ‎rename()‎ الذي يقبل مسار الملف المراد نقله كمعامل أول، ثم المسار الجديد الوجهة كمعامل ثانِ، ففي حالتنا نريد استخدام الدالة ‎moveFile()‎ لنقل الملف الجديد ‎greetings-2.txt‎ إلى المجلد الذي أنشأناه ‎test-data‎ مع إعادة تسمية ذلك الملف إلى ‎salutations.txt‎، ولذلك نضيف الشيفرة التالية:

const fs = require('fs').promises;

async function moveFile(source, destination) {
  try {
    await fs.rename(source, destination);
    console.log(`Moved file from ${source} to ${destination}`);
  } catch (error) {
    console.error(`Got an error trying to move the file: ${error.message}`);
  }
}

moveFile('greetings-2.txt', 'test-data/salutations.txt');

كما ذكرنا سابقًا فالتابع ‎rename()‎ يقبل معاملين هما المسار المصدر والوجهة لنقل الملف، ويمكن استخدام هذا التابع إما لنقل الملفات من مجلد لآخر أو لإعادة تسمية الملفات، أو نقل وإعادة تسمية ملف ما معًا، وهو ما نريد تنفيذه في مثالنا.

والآن نحفظ الملف ونخرج منه وننفذه باستخدام الأمر ‎node‎ كالتالي:

$ node moveFile.js

ليظهر الخرج التالي:

Moved file from greetings-2.txt to test-data/salutations.txt

نستعرض الملفات الموجودة حاليًا بعد التنفيذ للتأكد من نجاح عملية النقل باستخدام الأمر ‎ls‎ كالتالي:

$ ls

ليظهر لنا الملفات والمجلدات التالية:

deleteFile.js   greetings.txt   moveFile.js     readFile.js     test-data       writeFile.js

ونستخدم الأمر ‎ls‎ مجددًا لعرض الملفات ضمن المجلد الوجهة ‎test-data‎:

$ ls test-data

ليظهر لنا الملف الذي نقلناه:

salutations.txt

وبذلك نكون قد تعلمنا كيف يمكن استخدام التابع ‎rename()‎ لنقل الملفات من مجلد لآخر مع إعادة تسمية الملف ضمن نفس العملية.

خاتمة

تعرفنا في هذا المقال على مختلف عمليات إدارة الملفات ضمن نود، بداية بقراءة محتوى الملفات باستخدام ‎readFile()‎ ثم إنشاء ملفات جديدة وكتابة البيانات إليها باستخدام ‎writeFile()‎ ثم طريقة حذف الملفات باستخدام ‎unlink()‎ ونقلها وإعادة تسميتها باستخدام ‎rename()‎.

إنَّ التعامل مع الملفات من المهام الضرورية في نود فقد تحتاج البرامج أحيانًا إلى تصدير بعض الملفات للمستخدم أو تخزين البيانات الخاصة بها ضمن الملفات لاستعادتها لاحقًا، ولذلك توفر الوحدة البرمجية ‎fs‎ في نود كل التوابع الضرورية للتعامل مع الملفات في نود، ويمكنك الرجوع إلى التوثيق الرسمي للوحدة البرمجية ‎fs‎ من نود للتعرف عليها أكثر.

ترجمة -وبتصرف- للمقال How To Work with Files using the fs Module in Node.js.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...