كثيرًا ما نحتاج للتعامل مع نظام الملفات، فمثلًا لتخزين بعض الملفات بعد تنزيلها، أو لترتيب بعض البيانات ضمن مجلدات أو لقراءة الملفات للتعامل مع محتوياتها ضمن بعض التطبيقات، وحتى تطبيقات النظم الخلفية أو أدوات واجهة سطر الأوامر CLI قد تحتاج أحيانًا لحفظ بعض البيانات إلى ملفات، والتطبيقات التي تتعامل مع البيانات تحتاج أحيانًا لتصديرها بمختلف الصيغ مثل JSON أو CSV أو ملفات برنامج إكسل، فكل تلك المتطلبات تحتاج للتعامل مع نظام الملفات ضمن نظام التشغيل التي تعمل عليه.
توفر نود طريقة برمجية للتعامل مع الملفات باستخدام الوحدة البرمجية fs
، وهي اختصار لجملة "نظام الملفات" أو "file system" حيث تحتوي على العديد من التوابع التي نحتاجها لقراءة الملفات أو الكتابة إليها أو حذفها، وهذه المزايا تجعل من لغة جافاسكربت لغة مفيدة لاستخدامها ضمن تطبيقات النظم الخلفية و أدوات سطر الأوامر.
سنتعرف في هذا المقال على الوحدة البرمجية fs
وسنستخدمها لقراءة الملفات وإنشاء ملفات جديدة والكتابة إليها وحذف الملفات وحتى نقل الملفات من مجلد إلى آخر، حيث توفر الوحدة البرمجية fs
توابع للتعامل مع الملفات بالطريقتين المتزامنة synchronously واللامتزامنة asynchronously وباستخدام مجاري البيانات streams، حيث سنستخدم في هذا المقال الطريقة اللامتزامنة باستخدام الوعود Promises وهي الطريقة الأكثر استخدامًا.
المستلزمات
-
نسخة نود مثبتة على الجهاز، والتي ستوفر الوحدة البرمجية
fs
التي سنتعامل معها، حيث استخدمنا في هذا المقال الإصدار رقم 10.22.0. - سنستخدم ضمن الأمثلة الوعود في جافاسكربت مع صيغة اللاتزامن والانتظار async/await للتعامل مع الملفات، وللتعرف أكثر على هذه الصيغة يمكنك مراجعة مقال طرق كتابة شيفرات غير متزامنة التنفيذ في Node.js من هذه السلسة.
قراءة الملفات باستخدام 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.