Menu
flag Ar iconالعربيةdown icon

قراءة الملفات وكتابتها في Golang: os وbufio والمجلدات

كيف تقرأ الملفات وتكتبها في Go: os.ReadFile وos.WriteFile، القراءة سطرًا بسطر بـ bufio.Scanner، الإلحاق بـ os.OpenFile، التحقّق من وجود ملف، والتعامل مع المجلدات.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

قراءة ملف كامل وكتابته

تغطّي 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 يعني أنه موجود. أي خطأ آخر (رفض الصلاحية مثلًا) يعني أنك لا تستطيع الجزم، فعالجه على حدة بدل معاملته كـ "غير موجود".

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن