Lesson ১৩পড়তে ৭ মিনিট লাগবে

ফাইল আই/ও বা ইনপুট/আউটপুট (File I/O)

ফাইলগুলো (Files) মূলত কোনো তাক বা শেলফে (shelf) রাখা নোটবইয়ের (notebooks) মতোই কাজ করে — একটি বই খুলুন (open), সেটা থেকে কিছু পড়ুন (read) বা সেখানে কিছু লিখুন (write), তারপর সেটিকে আবার জায়গামতো গুছিয়ে রেখে দিন

নোটবইয়ের (Notebook) উদাহরণ বা আ্যনালজি (Analogy)

ধরা যাক, একটি শেলফ (shelf) বা তাকে অনেকগুলো নোটবই (notebooks) রাখা আছে। আপনি যদি এদের মধ্যে থেকে যেকোনো একটিটিকে ব্যবহার করতে চান, তবে আপনাকে প্রথমেই:

  1. বইটিকে ওই তাক (shelf) থেকে নামিয়ে আনতে হবেfopen()
  2. এর পৃষ্ঠাগুলো (pages) পড়তে (Read) হবে অথবা এতে নতুন কিছু লিখতে (write) হবেfprintf(), fscanf(), fgets()
  3. কাজ শেষে জিনিসটিকে আবার আগের জায়গায় ফেরত রাখতে (Put it back) হবেfclose()

যাইহোক, যদি এখানকার ওই নোটবইটির (notebook) কোনো অস্তিত্বই না থাকে বা বইটির কোনো অস্তিত্বই না থাকে এবং এমন অবস্থায় যদি আপনি এটিকে পড়ার (read) চেষ্টা করেন, তবে আপনি এর ভেতর থেকে একটি NULL পয়েন্টার (pointer) ফেরত পাবেন। আর যদি আপনি এটিকে লিখতে (writing) বা লেখালেখি করার উদ্দেশ্যে খোলেন (open), তবে সি (C) প্রোগ্রামিং নিজে থেকেই আপনার জন্য একটি একদম নতুন নোটবই (brand-new notebook) তৈরি (create) করে নেবে।

fopen-এর মোড বা ধরনগুলো (fopen Modes)

এই fopen-এর দ্বিতীয় আর্গুমেন্টটি (argument) মূলত সি (C)-কে নির্দেশ (tells) দেয় যে, এখানকার ফাইলটিকে ঠিক কীভাবে (how) খোলা (open) উচিত:

  • "r" — শুধুমাত্র পড়ার (Read only) জন্য। ফাইলটির (File) অস্তিত্ব থাকা অবশ্যই আবশ্যক।
  • "w" — শুধুমাত্র লেখার (Write only) জন্য। নতুন ফাইল তৈরি করে অথবা এর আগের সব লেখাকে মুছে (erases existing content) ফেলে।
  • "a" — অ্যাপেন্ড বা যুক্ত করা (Append)। আগের লেখা মুছে না ফেলে ফাইলের একদম শেষের দিকে (end) নতুন কিছু লেখা (writes) যুক্ত করে।
  • "r+" — পড়া এবং লেখা (Read and write)। এর জন্য ফাইলটির (File) অস্তিত্ব থাকা অবশ্যই আবশ্যক।
  • "rb", "wb" — বাইনারি মোড বা বাইনারি ধরন (Binary mode) (যেমন যেকোনো ছবি বা images, অডিও বা audio ইত্যাদির জন্য)।

টেক্সট ফাইলে (Text File) লেখা (Writing)

#include <stdio.h>
int main() {
FILE *fp = fopen("scores.txt", "w");
if (fp == NULL) {
printf("Error: could not create file\n");
return 1;
}
fprintf(fp, "Alice: %d\n", 95);
fprintf(fp, "Bob: %d\n", 87);
fprintf(fp, "Carol: %d\n", 92);
fclose(fp);
printf("Scores written to scores.txt\n");
return 0;
}
Output
Scores written to scores.txt
Note: সবসময় নিশ্চিতভাবে চেক (check) করে নেবেন যে এই fopen কোনো NULL রিটার্ন (returns) করছে কি না — কারণ হয়তো এমন হতে পারে যে আপনার ফাইলটির (file) আসলেই কোনো অস্তিত্ব নেই, কিংবা হয়তো ফাইলটি নিয়ে কাজ করার মতো কোনো পারমিশন বা অনুমতি (permission) আপনার কাছে নেই, অথবা হয়তো বা আপনার এখানকার ডিস্কটিও (disk) পুরোপুরি ভর্তি বা কানায় কানায় পূর্ণ (full) হয়ে থাকতে পারে। কাজ শেষ হয়ে যাওয়ার পর (done) সবসময় ফাইলগুলোকে একটি fclose কল (call) করে বন্ধ করে দিন — কারণ মেমোরিতে (memory) সেভ না হওয়া কাজগুলো বা আনক্লোজড ফাইলগুলো (unclosed files) যেকোনো সময়েই বাফার হতে পারে বা বাফার্ড (buffered) হয়ে তাদের ডেটা (data) হারাতে পারে এমনকি এগুলো হয়তো কখনোই মূল ডিস্কে ফ্লাশড (flushed to disk) নাও হতে পারে।

লাইন ধরে বা লাইন বাই লাইন পড়া (Reading Line by Line)

fgets() আপনার দেওয়া একটি বাফারের (buffer) মধ্যে একবারে একটি করে লাইনকে (line) পড়তে (reads) থাকে। টেক্সট ফাইলগুলো (text files) পড়ার জন্য এটি হলো মূলত সবচেয়ে নিরাপদ (safest) উপায় বা পদ্ধতি, কারণ এতে আপনি আগে থেকেই একটি সর্বোচ্চ বা ম্যাক্সিমাম দৈর্ঘ্য (maximum length) ঠিক করে দিতে পারেন — এর ফলে এতে কোনো বাফার ওভারফ্লোর বা বাফার চুইয়ে পড়ার (buffer overflow risk) কোনো ধরনের ঝুঁকি বা রিস্ক থাকে না।

এখানের এই লুপ প্যাটার্নটি (loop pattern) while (fgets(buffer, size, fp) != NULL) মূলত ফাইলের একদম শেষ পর্যন্ত (end of the file) পড়তে থাকে। যখন পড়ার মতো আর কিছুই অবশিষ্ট থাকে না (nothing left), তখন এই fgets একটি NULL রিটার্ন (returns) করে।

fgets ব্যবহার করে লাইন ধরে বা লাইন বাই লাইন পড়া (Reading Line by Line with fgets)

#include <stdio.h>
int main() {
FILE *fp = fopen("scores.txt", "r");
if (fp == NULL) {
printf("Error: cannot open scores.txt\n");
return 1;
}
char line[256];
int lineNum = 1;
while (fgets(line, sizeof(line), fp) != NULL) {
printf("Line %d: %s", lineNum, line);
lineNum++;
}
fclose(fp);
return 0;
}
Output
Line 1: Alice: 95
Line 2: Bob: 87
Line 3: Carol: 92

বাইনারি ফাইল (Binary Files) — fread এবং fwrite

টেক্সট ফাইলগুলো (Text files) মূলত যেকোনো ডেটাকে (data) মানুষের পড়ার উপযোগী (human-readable) ক্যারেক্টার (characters) হিসেবে সেভ (store) করে রাখে। অন্যদিকে বাইনারি ফাইলগুলো (Binary files) এগুলোকে র বাইট (raw bytes) হিসেবে সেভ করে — অর্থাৎ ঠিক যেমনভাবে ডেটাগুলো মেমোরিতে (memory) দেখতে হয়। এই পদ্ধতিটি অনেকটাই দ্রুত (faster) এবং কম্প্যাক্ট (compact), কিন্তু আপনি চাইলে কখনোই একটি বাইনারি ফাইলকে নোটপ্যাডে (Notepad) খুলে সেটির ভেতরে কী লেখা আছে তা বুঝতে বা সেন্স (sense) বের করতে পারবেন না

এখানকার এই fwrite(data, elementSize, count, fp) মূলত এর এই র বাইটগুলোকে (raw bytes) লেখে (writes)। এবং এই fread(buffer, elementSize, count, fp) মূলত এগুলোকে আবার উলটে ফেরত পড়ে থাকে (reads them back)।

বাইনারি ফাইল পড়া/লেখা (Binary File Read/Write)

#include <stdio.h>
typedef struct {
char name[20];
int score;
} Record;
int main() {
// বাইনারি ডেটা বা binary data লিখুন (Write)
Record players[] = {
{"Alice", 950},
{"Bob", 870},
{"Carol", 920}
};
int count = 3;
FILE *fp = fopen("players.dat", "wb");
if (!fp) return 1;
fwrite(players, sizeof(Record), count, fp);
fclose(fp);
// বাইনারি ডেটা বা binary data পড়তে বা Read করতে ফিরে আসুন
Record loaded[3];
fp = fopen("players.dat", "rb");
if (!fp) return 1;
fread(loaded, sizeof(Record), count, fp);
fclose(fp);
for (int i = 0; i < count; i++) {
printf("%s: %d\n", loaded[i].name, loaded[i].score);
}
return 0;
}
Output
Alice: 950
Bob: 870
Carol: 920

ভুল বা এরর চেক করা (Error Checking) এবং EOF

রোবাস্ট ফাইল কোডগুলো (Robust file code) মূলত এর প্রতিটি ধাপে (every step) গিয়ে এর ভুল বা এররগুলোকে চেক (checks for errors) করে থাকে:

  • যেকোনো ধরনের ফেইলিয়ার (failure) বা ব্যর্থতার ক্ষেত্রে এটি fopen মূলত একটি NULL রিটার্ন বা ফেরত তৈরি (returns) করে
  • এন্ড-অফ-ফাইলে (end-of-file) (বা এররে বা ত্রুটিতে) পৌঁছালে fgets মূলত একটি NULL রিটার্ন (returns) করে
  • fread মূলত এটি ঠিক কতগুলো আইটেম বা আইটেমসমূহকে (items) সত্যি সত্যি পড়েছে বা রিড করেছে (read) তার একটি নির্দিষ্ট সংখ্যা রিটার্ন (returns) করে — যদি এটি আপনার চাওয়া পরিমাণের চেয়েও কম হয়, তবে আপনি হয়তো EOF বা এন্ড-অফ-ফাইলে অথবা কোনো এররে (error) গিয়ে আটকে পড়েছেন
  • আপনি যদি ফাইলটির (file) একেবারে শেষে গিয়ে থাকেন তবে এই feof(fp) মূলত ট্রু (true) রিটার্ন বা ফেরত (returns) করে থাকে
  • যদি এটি পড়ার/লেখার (read/write) সময় কোনো এরর বা ত্রুটির (error occurred) সম্মুখীন হয়ে থাকে, তবে এই ferror(fp) মূলত ট্রু বা সত্য (true) রিটার্ন বা ফেরত দিয়ে থাকে

রোবাস্ট বা শক্তিশালী এরর চেকিং (Robust Error Checking)

#include <stdio.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("fopen failed");
// এখানকার perror মূলত সিস্টেমের (system) এরর মেসেজটি (error message) প্রিন্ট বা prints করে
return 1;
}
char buf[100];
while (fgets(buf, sizeof(buf), fp)) {
printf("%s", buf);
}
if (ferror(fp)) {
printf("A read error occurred!\n");
} else if (feof(fp)) {
printf("Reached end of file.\n");
}
fclose(fp);
return 0;
}
Output
fopen failed: No such file or directory
Note: কোনো একটি ফাইলকে (file) "w" মোডের (mode) মাধ্যমে খোলার অর্থ হলো, আপনি এর ভেতরের সবকিছু মুছে (ERASE) ফেলতে চলেছেন! আপনি যদি এখানকার পুরোনো লেখা বা কন্টেন্টগুলোকে (contents) একটুও ডেসট্রয় (destroying) বা নষ্ট না করে এর সাথে নতুন কিছু যোগ (add) করতে চান, তবে আপনাকে এর বদলে "a" (অ্যাপেন্ড বা append) মোডটিকে (mode) ব্যবহার করতে হবে।
চ্যালেঞ্জ

ছোট কুইজ

যদি কোনো ফাইল খোলা না যায় তবে তখন fopen আসলে কী रिटर्न বা ফেরত (return) দিয়ে থাকে?
Structs & TypedefPreprocessor & Macros