المقال الأصلي

تمت الترجمة بواسطة Naser Dakhel

إذا كتبت برنامج helloworld بلغة C مسبقًا، فهذا يعني أنك تعلم أساسيات دخل وخرج الملفات في اللغة:

// مثال بسيط لبرنامج ‫helloworld في لغة C 
#include <stdlib.h>

// استيراد دوال الدخل والخرج
#include <stdio.h>

int main()
    // تقوم دالة‫ printf بكل هذا العمل لوحدها ضمن البرنامج
    printf("Hello, world!\n");
    return EXIT_SUCCESS;
}

يعدّ التعامل مع الملفات من أهم أجزاء البرمجة، ونستخدم في لغة C هيكل structure مؤشر pointer من نوع FILE للتصريح عن ملف:

FILE *fp;

تقدم لغة C عددًا من الدوال functions المُضمنّة built-in في اللغة للقيام ببعض من العمليات الأساسية للتعامل مع الملفات:

  • fopen()‎ - تُنشئ ملفًا جديدًا أو تفتح ملفًا موجودًا.
  • fclose()‎ - تُغلق ملفًا.
  • getc()‎ - تقرأ محرف من ملف.
  • putc()‎ - تكتب محرفًا إلى ملف.
  • fscanf()‎ تقرأ مجموعة من البيانات من ملف.
  • fprintf()‎ - تكتب مجموعة من البيانات إلى ملف.
  • getw()‎ - تقرأ عددًا صحيحًا integer من ملف.
  • putw()‎ - تكتب عددًا صحيحًا إلى ملف.
  • fseek()‎ - ضبط موضع الكتابة -أو القراءة- إلى نقطة محدّدة.
  • ftell()‎ - تُعيد موضع الكتابة -أو القراءة- الحالي.
  • rewind()‎ تضبط موضع الكتابة -أو القراءة- إلى نقطة البداية.

فتح ملف

تُستخدم الدالة fopen()‎ لإنشاء ملف أو فتح ملف موجود مسبقًا:

fp = fopen(const char filename,const char mode);

هناك عدة أنماط modes لفتح الملف:

  • r - فتح الملف في نمط القراءة.
  • w - فتح أو إنشاء ملف نصي في نمط القراءة.
  • a - فتح ملف في نمط الإضافة append.
  • r+‎ - فتح ملف في نمطَي القراءة والكتابة.
  • a+‎ - فتح ملف في نمطَي القراءة والكتابة.
  • w+‎ - فتح ملف في نمطَي القراءة والكتابة.

إليك مثالًا عن قراءة وكتابة بيانات إلى ملف:

#include<stdio.h>
#include<conio.h>
main()
{
FILE *fp;
char ch;
fp = fopen("hello.txt", "w");
printf("Enter data");
while( (ch = getchar()) != EOF) {
  putc(ch,fp);
}
fclose(fp);
fp = fopen("hello.txt", "r");

while( (ch = getc(fp)! = EOF)
  printf("%c",ch);
 
fclose(fp);
}

لعلّ السؤال التالي يتبادر إلى ذهنك: "البرنامج السابق يطبع النص إلى الشاشة مباشرةً، كيف له أن يكتب ويقرأ من الملف؟".

لن تكون الإجابة واضحة للوهلة الأولى، وسيتطلب الأمر فهمًا لأنظمة يونيكس UNIX؛ ففي أنظمة يونيكس تُعامل جميع الأشياء كملفات مما يعني أنه بإمكانك الكتابة إليها والقراءة منها.

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

كيف يرتبط ما سبق بموضوع مثال helloworld ودخل وخرج الملفات؟

عندما نستدعي الدالة printf، فنحن نكتب المعلومات إلى ملف خاص يدعى stdout، وهو اختصار لكلمة الخرح القياسي Standard Output وتمثّل stdout الخرج القياسي المُحدد من قبل صدفتك Shell وهو الطرفية Terminal غالبًا، مما يفسر طباعة المعلومات على شاشتك عند استدعاء الدالة.

هناك نوعان متاحان من التدفق streams -أو الملفات- وهُما stdin وstderr. تعني stdin الدخل القياسي Standard Input التي تربطه صدفتك بشكل افتراضي إلى لوحة المفاتيح، بينما تعني stderr الخطأ القياسي Standard Error التي تربط صدفتك بشكل افتراضي إلى طرفيتك.

دخل وخرج الملفات البدائي

دعنا نتوقف عن الكلام النظري ونتوجه إلى القسم العملي وكتابة الشيفرات البرمجية! الوسيلة الأسهل للكتابة إلى ملف تكمن بتوجيه تدفق الخرج باستخدام أداة إعادة التوجيه <، ويمكنك استخدام << إذا أردت أن تُضيف إلى الملف:

# سيُطبع الخرج على الشاشة بعد تنفيذ التالي
./helloworld
# سيُكتب الخرج إلى الملف في الحالة التالية
./helloworld > hello.txt

ستكون محتويات الملف النصي hello.txt بشكل متوقع كالتالي:

Hello, world!

دعنا نُنشئ برنامجًا جديدًا باسم greet مماثلًا لبرنامج helloworld السابق، ولكننا في هذه الحالة سنرحّب بالمستخدم باستخدام اسمه المخزّن في المصفوفة name:

#include <stdio.h>
#include <stdlib.h>

int main() {
    // تهيئة مصفوفة محارف لتخزين الاسم
    char name[20];
    // قراءة السلسلة النصية وإسنادها إلى‫ name
    scanf("%s", name);
    // طباعة التحية
    printf("Hello, %s!", name);
    return EXIT_SUCCESS;

يمكننا إعادة توجيه stdin للقراء من الملف عوضًا عن القراءة من لوحة المفاتيح مباشرةً، وذلك باستخدام الأداة >:

# اكتب ملفًا يحتوي على اسم
echo Kamala > name.txt
# سيتسبب ما يلي بقراءة الاسم من الملف وطباعة التحية إلى الشاشة
./greet < name.txt
# ==> Hello, Kamala!
# يمكنك استخدام "<" إذا أردت كتابة التحية إلى ملف

ملاحظة: العوامل operators المذكورة هنا مُستخدمة في bash والصدفات المشابهة الأخرى.

حان وقت التعمّق في الأمر!

تعمل الطرق السابقة فقط في حالات التوظيف البسيطة، ويجب عليك غالبًا العمل مع الملفات بداخل لغة C إن أردت إنجاز مهام أكبر وأكثر تعقيدًا بدلًا من استخدام الصدفة.

لإنجاز ذلك، ستستخدم الدالة fopen والتي تأخذ سلسلتين نصيتين string كوسيطين parameters، وتدل السلسلة النصية الأولى على اسم الملف والثانية على نمط فتح الملف (ذكرنا الأنماط في الفقرة الأولى).

يُنظر إلى الأنماط بكونها سماحيات للعمليات على الملف، مثل r للقراءة وw للكتابة ,a للإضافة، ويمكنك الجمع فيما بين الأنماط؛ فعلى سبيل المثال يعني النمط rw أنه بإمكانك قراءة الملف والكتابة إليه في ذات الوقت، وهناك المزيد من الأنماط إلا أننا ذكرنا الأنماط الأكثر شيوعًا.

يمكنك استخدام أوامر الدخل والخرج الأساسية بعد حصولك على مؤشر من نوع FILE بشكل مشابع لما سبق، عدا أنه عليك الآن البدء بالحرف f عند كتابة الأوامر (الدوال) وسيكون أول وسطاء الدالة هو مؤشر الملف. على سبيل المثال، تصبح الدالة printf بالشكل fprintf.

إليك برنامجًا باسم greetings، يقرأ من ملف يحتوي على لائحة من الأسماء ويكتب التحيّات إلى ملف آخر:

#include <stdio.h>
#include <stdlib.h>

int main() {
    // إنشاء مؤشرات الملف
    FILE *names = fopen("names.txt", "r");
    FILE *greet = fopen("greet.txt", "w");

    // التحقق من عدم وجود أخطاء
    if (!names || !greet) {
        fprintf(stderr, "File opening failed!\n");
        return EXIT_FAILURE;
    }

    // وقت إلقاء التحية!‏
    char name[20];
    // استمرّ بالقراءة حتى تصل لنهاية لائحة الأسماء
    while (fscanf(names, "%s\n", name) > 0) {
        fprintf(greet, "Hello, %s!\n", name);
    }

    // اطبع رسالة إلى الطرفية لإعلام المستخدم بانتهاء البرنامج
    if (feof(names)) {
        printf("Greetings are done!\n");
    }

    return EXIT_SUCCESS;
}

بفرض أن الملف names.txt يحتوي على التالي:

Kamala
Logan
Carol

نحصل على الملف greet.txt بعد تشغيل البرنامج greetings، وسيتضمن التالي:

Hello, Kamala!
Hello, Logan!