المسارات
/
Go
Go
/
التمارين
/
تفكيك ملفات السجلات
تفكيك ملفات السجلات

تفكيك ملفات السجلات

تمرين تعلّمي

مقدمة

توفّر الحزمة regexp دعمًا للتعبيرات النمطية في Go.

الصياغة

إنّ الصياغة الخاصة بالتعبيرات النمطية المقبولة هي الصياغة العامة نفسها التي تستخدمها Perl و Python ولغات أخرى.

يُفسَّر كل من أنماط البحث والنصوص المُدخَلة بترميز UTF-8.

عند استخدام العلامات الخلفية (`) لإنشاء السلاسل النصية، لا يكون للشرطات المائلة العكسية (\) أي معنى خاص، ولا تدلّ على بداية محارف خاصة مثل علامات الجدولة \t أو الأسطر الجديدة \n:

"\t\n" // regular string literal with 2 characters: a tab and a newline
`\t\n`// raw string literal with 4 characters: two backslashes, a 't', and an 'n'

لهذا السبب، من المستحسن استخدام العلامات الخلفية لإنشاء التعبيرات النمطية، لأن ذلك يعني أننا لا نحتاج إلى تخطّي الشرطات المائلة العكسية:

"\\" // string with a single backslash
`\\` // string with 2 backslashes

تصريف الأنماط - نوع RegExp

لاستخدام تعبير نمطي، علينا أولًا تصريف نمط السلسلة النصية. التصريف هنا يعني أخذ نمط السلسلة النصية للتعبير النمطي وتحويله إلى تمثيل داخلي يسهل التعامل معه. نحتاج إلى تصريف كل نمط مرة واحدة فقط، وبعد ذلك يمكننا استخدام النسخة المصرّفة من التعبير النمطي مرات عديدة. يمثّل النوع regexp.Regexp تعبيرًا نمطيًا مصرّفًا. يمكننا تصريف نمط سلسلة نصية إلى regexp.Regexp باستخدام الدالة regexp.Compile. تُرجع هذه الدالة nil وخطأً إذا فشل التصريف:

re, err := regexp.Compile(`(a|b)+`)
fmt.Println(re, err) // => (a|b)+ <nil>
re, err = regexp.Compile(`a|b)+`)
fmt.Println(re, err) // => <nil> error parsing regexp: unexpected ): `a|b)+`

تُعدّ الدالة MustCompile بديلًا مريحًا للدالة Compile:

re = regexp.MustCompile(`[a-z]+\d*`)

باستخدام هذه الدالة، لا حاجة إلى التعامل مع خطأ.

Caution

ينبغي استخدام MustCompile فقط عندما نتأكد من أن النمط يُصرَّف، وإلا فسيدخل البرنامج في حالة panic.

طرق التعبيرات النمطية

هناك 16 طريقة في Regexp تطابق تعبيرًا نمطيًا وتحدّد النص المطابق. تتطابق أسماءها مع هذا التعبير النمطي:

Find(All)?(String)?(Submatch)?(Index)?
  • إذا وُجد All، تُطابق الطريقة المطابقات المتتالية غير المتداخلة للتعبير بأكمله.
  • إذا وُجد String، فإن الوسيط سلسلة نصية؛ وإلا فهو شريحة من البايتات؛ وتُعدَّل القيم المُرجَعة وفقًا لذلك.
  • إذا وُجد Submatch، فإن القيمة المُرجَعة شريحة تحدّد المطابقات الفرعية المتتالية للتعبير.
  • إذا وُجد Index، تُحدَّد المطابقات والمطابقات الفرعية عبر أزواج فهارس البايتات داخل سلسلة الإدخال.

وهناك أيضًا طرق من أجل:

  • استبدال مطابقات التعبيرات النمطية بسلاسل نصية بديلة، و
  • تقسيم السلاسل النصية المفصولة بالتعبيرات النمطية.

إجمالًا، تعرّف حزمة regexp أكثر من 40 دالة وطريقة. سنوضّح أدناه استخدام بعض الطرق. يُرجى الرجوع إلى توثيق API لمعرفة تفاصيل هذه الدوال وغيرها.

أمثلة على MatchString

تُبلّغ الطريقة MatchString عمّا إذا كانت سلسلة نصية تحتوي على أي مطابقة لتعبير نمطي.

re = regexp.MustCompile(`[a-z]+\d*`)
b = re.MatchString("[a12]")       // => true
b = re.MatchString("12abc34(ef)") // => true
b = re.MatchString(" abc!")       // => true
b = re.MatchString("123 456")     // => false

أمثلة على FindString

تُرجع الطريقة FindString سلسلة نصية تحمل نص أول مطابقة من اليسار للتعبير النمطي.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.FindString("[a12]")       // => "a12"
s = re.FindString("12abc34(ef)") // => "abc34"
s = re.FindString(" abc!")       // => "abc"
s = re.FindString("123 456")     // => ""

أمثلة على FindStringSubmatch

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

re = regexp.MustCompile(`[a-z]+(\d*)`)
sl = re.FindStringSubmatch("[a12]")       // => []string{"a12","12"}
sl = re.FindStringSubmatch("12abc34(ef)") // => []string{"abc34","34"}
sl = re.FindStringSubmatch(" abc!")       // => []string{"abc",""}
sl = re.FindStringSubmatch("123 456")     // => <nil>

أمثلة على ReplaceAllString

تُرجع الطريقة re.ReplaceAllString(src,repl) نسخة من src، مع استبدال مطابقات التعبير النمطي re بالسلسلة النصية البديلة repl.

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.ReplaceAllString("[a12]", "X")       // => "[X]"
s = re.ReplaceAllString("12abc34(ef)", "X") // => "12X(X)"
s = re.ReplaceAllString(" abc!", "X")       // => " X!"
s = re.ReplaceAllString("123 456", "X")     // => "123 456"

أمثلة على Split

تقطّع الطريقة re.Split(s,n) نصًا s إلى سلاسل نصية فرعية مفصولة بالتعبير، وتُرجع شريحة بالسلاسل النصية الفرعية الواقعة بين مطابقات ذلك التعبير. ويحدّد العدد n الحدّ الأقصى لعدد السلاسل النصية الفرعية المُرجَعة. وإذا كان n<0، تُرجع الطريقة كل السلاسل النصية الفرعية.

re = regexp.MustCompile(`[a-z]+\d*`)
sl = re.Split("[a12]", -1)      // => []string{"[","]"}
sl = re.Split("12abc34(ef)", 2) // => []string{"12","(ef)"}
sl = re.Split(" abc!", -1)      // => []string{" ","!"}
sl = re.Split("123 456", -1)    // => []string{"123 456"}

التعليمات

يتناول هذا التمرين تحليل ملفات السجل.

بعد مراجعة أمنية حديثة، طُلب منك تنظيف ملفات السجل المؤرشفة في المؤسسة.

جميع السلاسل النصية التي تُمرَّر إلى الدوال مضمونة بأنها ليست null ولا تحتوي على مسافات في بدايتها أو نهايتها.

1. تحديد أسطر السجل المشوّشة

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

  • [TRC]
  • [DBG]
  • [INF]
  • [WRN]
  • [ERR]
  • [FTL]

نفّذ الدالة IsValidLine لتُرجع false إذا كانت السلسلة النصية غير صالحة، وtrue خلاف ذلك.

IsValidLine("[ERR] A good error here")
// => true
IsValidLine("Any old [ERR] text")
// => false
IsValidLine("[BOB] Any old text")
// => false

2. تقسيم سطر السجل

انضم فريق جديد إلى المؤسسة، وتكتشف أن ملفات السجل الخاصة به تستخدم فاصلًا غريبًا بين "الحقول". فبدلًا من فاصل منطقي مثل النقطتين ":"، يستخدمون سلسلة نصية مثل "<--->" أو "<=>" (لأنها أجمل)، بل أي سلسلة نصية يبدأ أول حرف فيها بـ"<" وينتهي آخر حرف فيها بـ">" ويقع بينهما أي مزيج من الأحرف "~" و"*" و"=" و"-".

نفّذ الدالة SplitLogLine التي تأخذ سطرًا وتُرجع مصفوفة من السلاسل النصية، تحتوي كل واحدة منها على حقل.

SplitLogLine("section 1<*>section 2<~~~>section 3")
// => []string{"section 1", "section 2", "section 3"},

3. عدّ الأسطر التي تحتوي على password داخل نص بين علامتي اقتباس

يحتاج الفريق إلى معرفة الإشارات إلى كلمات المرور في النصوص المقتبسة حتى يمكن فحصها يدويًا.

نفّذ الدالة CountQuotedPasswords لتقديم مؤشر على الحجم المحتمل للجهد اليدوي.

حدّد أسطر السجل التي تكون فيها السلسلة النصية "password"، التي قد تأتي بأي مزيج من الأحرف الكبيرة أو الصغيرة، محاطة بعلامتي اقتباس. ينبغي أن تراعي احتمال وجود محتوى إضافي بين علامتي الاقتباس قبل "password" وبعده. سيحتوي كل سطر على علامتي اقتباس على الأكثر.

قد تكون الأسطر التي تُمرَّر إلى الدالة صالحة كما هو معرّف في المهمة 1 أو لا تكون. نعالجها بالطريقة نفسها، سواء كانت صالحة أم لا.

lines := []string{
    `[INF] passWord`, // contains 'password' but not surrounded by quotation marks
    `"passWord"`,  // count this one
    `[INF] User saw error message "Unexpected Error" on page load.`, // does not contain 'password'
    `[INF] The message "Please reset your password" was ignored by the user`, // count this one
}
// => 2

4. إزالة المخلفات من السجل

لاحظت أن بعض المعالجة الأولية للسجلات كانت تنثر النص "end-of-line" متبوعًا برقم سطر (بدون مسافة فاصلة) في أنحاء السجلات.

نفّذ الدالة RemoveEndOfLineText لتأخذ سلسلة نصية وتزيل نص نهاية السطر وتُرجع سلسلة نصية "نظيفة".

تُعاد الأسطر التي لا تحتوي على نص نهاية السطر دون تعديل.

أزل سلسلة نهاية السطر فقط. لا تحاول تعديل المسافات البيضاء.

RemoveEndOfLineText("[INF] end-of-line23033 Network Failure end-of-line27")
// => "[INF]  Network Failure "

5. وسم الأسطر بأسماء المستخدمين

لاحظت أن بعض أسطر السجل تتضمّن جملًا تشير إلى مستخدمين. تحتوي هذه الجمل دائمًا على السلسلة النصية "User"، يتبعها حرف مسافة واحد أو أكثر، ثم اسم مستخدم. قرّرت وسم هذه الأسطر.

نفّذ دالة TagWithUserName تعالج أسطر السجل:

  • الأسطر التي لا تحتوي على السلسلة النصية "User " تبقى دون تغيير.
  • أما الأسطر التي تحتوي على السلسلة النصية "User "، فأضف إليها في البداية [USR] متبوعًا باسم المستخدم.

على سبيل المثال:

result := TagWithUserName([]string{
    "[WRN] User James123 has exceeded storage space.",
	"[WRN] Host down. User   Michelle4 lost connection.",
	"[INF] Users can login again after 23:00.",
	"[DBG] We need to check that user names are at least 6 chars long.",
})
// => []string {
//  "[USR] James123 [WRN] User James123 has exceeded storage space.",
//  "[USR] Michelle4 [WRN] Host down. User   Michelle4 lost connection.",
//  "[INF] Users can login again after 23:00.",
//  "[DBG] We need to check that user names are at least 6 chars long."
// }

يمكنك افتراض ما يلي:

  • أسماء المستخدمين يتبعها في السجل حرف مسافة بيضاء واحد على الأقل.
  • لا يظهر النص "User " أكثر من مرة واحدة في كل سطر.
  • أسماء المستخدمين سلاسل نصية غير فارغة ولا تحتوي على مسافات بيضاء.
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Go Exercism

مستعد لبدء تفكيك ملفات السجلات؟

سجّل في Exercism لتتعلّم وتتقن Go عبر 34 مفهومًا165 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.