قراءة ملف كامل وكتابته
تغطّي os.WriteFile وos.ReadFile معظم الحاجات. تفتحان وتكتبان أو تقرآن وتغلقان في استدعاء واحد.
تعمل أمثلة هذه الصفحة في مجلد مؤقّت من os.MkdirTemp وتحذفه بـ defer os.RemoveAll(dir)، فلا تترك أثرًا. في شيفرتك يُحلّ المسار النسبي مثل "config.json" نسبة إلى مجلد العمل للعملية، وهو ليس بالضرورة مجلد الملف المصدري أو الملف التنفيذي.
تنشئ os.WriteFile الملف عند الحاجة وتفرّغه إن كان موجودًا. الوسيط الثالث صلاحية Unix للملف المنشأ حديثًا: 0o644 تعني أن المالك يقرأ ويكتب، والبقية يقرؤون. ويُتجاهل للملف الموجود مسبقًا، وقد يزيل umask العملية بعض البتات.
تقرأ os.ReadFile كل شيء إلى الذاكرة. هذا صحيح لملفات الإعدادات والمدخلات الصغيرة، وخاطئ لسجل بحجم عدة غيغابايت.
القراءة سطرًا بسطر بـ bufio.Scanner
للملفات الكبيرة، أو عندما تريد الأسطر أصلًا، استخدم bufio.Scanner. يقرأ على دفعات ويسلّمك سطرًا في كل مرة، دون محرف السطر الجديد.
ثلاث تفاصيل:
- افحص
sc.Err()بعد الحلقة. تعيدScanالقيمةfalseعند نهاية الملف وعند الخطأ على حد سواء، وErrوحدها تميّز بينهما. - حد السطر 64 KB. افتراضيًا يوقف السطر الواحد الأطول من 64 KB الماسح برسالة
bufio.Scanner: token too long. للملفات ذات الأسطر الطويلة (JSON مضغوط، وبعض السجلات) ارفع الحد قبل الحلقة:sc.Buffer(make([]byte, 1024*1024), 10*1024*1024). - وحدات أخرى. يعطي
sc.Split(bufio.ScanWords)كلمات؛ ويعطيbufio.ScanRunesأحرفًا.
لقراءة تدفّق على دفعات ثابتة الحجم بدل الأسطر، استخدم f.Read(buf) في حلقة أو io.Copy إلى كاتب آخر.
الكتابة: os.Create وos.OpenFile والإلحاق
تفتح os.Create(name) ملفًا للكتابة، فتنشئه أو تفرّغه. وتعطي os.OpenFile تحكّمًا كاملًا عبر الأعلام:
| العلم | المعنى |
|---|---|
os.O_RDONLY، os.O_WRONLY، os.O_RDWR | الفتح للقراءة أو الكتابة أو كليهما (اختر واحدًا) |
os.O_CREATE | إنشاء الملف إن لم يكن موجودًا |
os.O_TRUNC | تفريغ الملف عند فتحه |
os.O_APPEND | كل كتابة تذهب إلى النهاية |
os.O_EXCL | مع O_CREATE: الفشل إذا كان الملف موجودًا مسبقًا |
os.Open(name) تساوي OpenFile(name, O_RDONLY, 0). وos.Create(name) تساوي OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).
عند الكتابة يهمّ الخطأ الصادر عن Close. بعض أنظمة الملفات لا تبلغ عن فشل الكتابة إلا وقت الإغلاق، فقد تخفي defer f.Close() وحدها بيانات ضائعة. للملفات التي تكتب فيها افحص Close صراحة كما تفعل appendLine. وللملفات التي تقرؤها فقط لا بأس بـ defer f.Close().
تقبل fmt.Fprintln وبقية دوال الطباعة في fmt أي io.Writer، ومنها الملف. يجمّع bufio.Writer الكتابات الصغيرة في الذاكرة. نسيان w.Flush() خطأ كلاسيكي: يخرج البرنامج بشكل طبيعي ولا تصل الكيلوبايتات الأخيرة إلى الملف أبدًا.
هل الملف موجود؟
لا تملك Go دالة os.Exists. استدعِ os.Stat وافحص الخطأ:
errors.Is(err, fs.ErrNotExist) هو الأسلوب المعتمد حاليًا. يحلّ محل os.IsNotExist(err) الأقدم، الذي لا يرى عبر الأخطاء المغلّفة.
الفحص قبل الفتح غالبًا غير ضروري ومعرّض للتسابق: قد يظهر الملف أو يختفي بين الفحص والفتح. عادة تفتحه مباشرة وتعالج fs.ErrNotExist الصادر عن الفتح. لإنشاء ملف فقط إن لم يكن موجودًا بعد، استخدم O_CREATE|O_EXCL، الذي يجعل الفحص والإنشاء خطوة ذرّية واحدة.
المجلدات
| المهمة | الدالة |
|---|---|
| إنشاء مجلد واحد | os.Mkdir(path, 0o755) |
| إنشاء مسار مع آبائه | os.MkdirAll(path, 0o755) |
| سرد محتوى مجلد | os.ReadDir(path) |
| المرور على شجرة | filepath.WalkDir(root, fn) |
| حذف ملف أو مجلد فارغ | os.Remove(path) |
| حذف شجرة | os.RemoveAll(path) |
| إعادة التسمية أو النقل | os.Rename(old, new) |
| ملف أو مجلد مؤقّت | os.CreateTemp("", "prefix-*")، os.MkdirTemp("", "prefix") |
| ضمّ أجزاء المسار | filepath.Join(a, b, c) |
استخدم path/filepath لمسارات نظام الملفات: فهي تستخدم الفاصل الصحيح لنظام التشغيل (\ في Windows). أما الحزمة path فللمسارات المفصولة بشرطة مائلة مثل عناوين URL.
أضافت Go 1.24 أيضًا os.Root (os.OpenRoot(dir))، الذي يفتح الملفات داخل مجلد واحد فقط ويرفض المسارات التي تخرج منه بـ .. أو بالروابط الرمزية. استخدمه عندما تأتي أسماء الملفات من المستخدمين.
أخطاء شائعة
- عدم فحص الأخطاء. كل واحد من هذه الاستدعاءات قد يفشل. الملف الذي فشل فتحه يكون
nil، وكلReadأوWriteأوCloseلاحقة عليه تعيدinvalid argument، فتخفي السبب الحقيقي (الملف مفقود، أو الصلاحية مرفوضة). - نسيان
sc.Err()بعد حلقة المسح. يبدو خطأ القراءة كنهاية الملف. - نسيان
Flushعلىbufio.Writer. تنقص نهاية الملف. - تجاهل الخطأ الصادر عن
Closeبعد الكتابة. قد لا تظهر أخطاء الكتابة إلا هناك. defer f.Close()داخل حلقة على ملفات كثيرة. تبقى الملفات مفتوحة حتى تعود الدالة وقد تنفد واصفات الملفات. انقل جسم الحلقة إلى دالة ليُغلق كل ملف في تكراره.- كتابة الصلاحيات بالنظام العشري.
644ليست0o644. تقرأ Go القيمة644كرقم عشري، أي0o1204، فتضبط بتات غريبة.
الأسئلة الشائعة
كيف أقرأ ملفًا كاملًا إلى نص في Go؟
تعيد data, err := os.ReadFile("notes.txt") المحتوى كـ []byte؛ حوّله بـ string(data). تفتح الملف وتقرؤه وتغلقه نيابة عنك. استخدمها للملفات التي تتّسع لها الذاكرة بسهولة؛ أما الملفات الكبيرة فاقرأها سطرًا بسطر بـ bufio.Scanner.
كيف أقرأ ملفًا سطرًا بسطر في Go؟
افتح الملف بـ os.Open، ثم defer f.Close()، وغلّفه بـ bufio.NewScanner(f)، ودُر في حلقة for sc.Scan() { line := sc.Text() }، وافحص sc.Err() بعد الحلقة. الأسطر الأطول من 64 KB تجعل الماسح يفشل برسالة token too long ما لم تكبّر مخزنه بـ sc.Buffer.
كيف ألحق نصًا بملف في Go؟
افتحه بـ os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)، واكتب، وافحص الخطأ الصادر عن Close. ينشئ O_CREATE الملف إن لم يكن موجودًا، ويجعل O_APPEND كل كتابة تذهب إلى النهاية.
كيف أتحقّق من وجود ملف في Go؟
استدعِ os.Stat(path) وافحص الخطأ بـ errors.Is(err, fs.ErrNotExist). الخطأ nil يعني أنه موجود. أي خطأ آخر (رفض الصلاحية مثلًا) يعني أنك لا تستطيع الجزم، فعالجه على حدة بدل معاملته كـ "غير موجود".