برمجة الصدفة والأتمتة

معالجة النصوص: grep وsed وawk

22 دقيقة الدرس 7 من 28

معالجة النصوص: grep وsed وawk

في بيئة الإنتاج، النصوص الخام في كل مكان — سجلات التطبيقات التي تحتوي على آلاف الأسطر، وسجلات وصول nginx التي تنمو بعشرات الآلاف من الطلبات في الدقيقة، وملفات CSV المُصدَّرة من أنظمة المراقبة، وملفات الإعدادات الممتدة عبر مئات الخوادم. القدرة على البحث والاستخراج والتحويل والتلخيص لهذا النص من سطر الأوامر — دون كتابة سكريبت Python، دون فتح ملف في محرر، دون انتظار تحديث لوحة تحكم — تُعدّ من أعلى المهارات المردودية لمهندس DevOps. ثلاث أدوات تتولى العمل الشاق: grep وsed وawk. لكل أداة غرض متميز، وتتكامل الثلاثة بسلاسة عبر الأنابيب (pipes).

النموذج الذهني: فكّر في الأدوات الثلاث كخط معالجة ذو قدرة متزايدة. grep يختار الأسطر. sed يحوّل النص (استبدال وحذف على مستوى الحروف). awk يحسب على الحقول المُهيكَلة (عمليات حسابية، شروط، تجميعات). عند الشك، استخدم الأداة الأضعف التي تحل المشكلة — سطر أوامر grep أسرع في الكتابة والتنفيذ وأسهل في القراءة لمن يأتي بعدك.

grep — البحث في النصوص على نطاق واسع

يطبع grep كل سطر من المدخلات يطابق نمطًا معينًا. اسمه مشتق من أمر المحرر ed وهو g/re/p (مطابقة عالمية لتعبير نظامي وطباعته). لغة النمط الافتراضية هي التعبيرات النظامية الأساسية (BRE)؛ العلَم -E يُفعّل التعبيرات النظامية الموسّعة (ERE)، و-P يُفعّل التعبيرات النظامية المتوافقة مع Perl (PCRE) على الأنظمة التي تدعمها.

الأعلام التي ستستخدمها يوميًا في الإنتاج:

  • -i — مطابقة غير حساسة لحالة الأحرف
  • -v — عكس المطابقة (طباعة الأسطر التي لا تطابق)
  • -r / -R — بحث متكرر في المجلدات
  • -l — طباعة أسماء الملفات فقط التي تحتوي على تطابق
  • -n — إضافة رقم السطر قبل كل سطر في المخرجات
  • -c — طباعة عدد الأسطر المتطابقة لكل ملف
  • -A N / -B N / -C N — طباعة N سطرًا بعد / قبل / حول كل تطابق (السياق)
  • -o — طباعة الجزء المتطابق فقط من السطر وليس السطر بأكمله
  • -E — تعبيرات نظامية موسّعة (التبديل |، المحددات الكمية +، ?، التجميع ())
  • --color=auto — تمييز التطابقات بالألوان (اضبطه في ملف تعريف shell لديك)
# إيجاد جميع أسطر ERROR في سجل التطبيق grep "ERROR" /var/log/app/app.log # بحث غير حساس لحالة الأحرف عن "timeout" مع سطرين من السياق grep -i -C 2 "timeout" /var/log/nginx/error.log # حساب استجابات HTTP 5xx لكل مضيف افتراضي في سجل الوصول grep -c " 5[0-9][0-9] " /var/log/nginx/access.log # إيجاد ملفات .env في مستودع (مفيد في تدقيقات الأمان) grep -rn --include="*.php" "DB_PASSWORD" /var/www/ # تعبير نظامي موسّع: مطابقة أسطر تحتوي "OOM" أو "killed" (غير حساس للحالة) grep -Ei "(oom|killed)" /var/log/syslog # عرض عناوين IP فقط من أسطر سجل nginx التي أنتجت أخطاء 500 grep " 500 " /var/log/nginx/access.log | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' # إيجاد الأسطر التي لا تحتوي "200" أو "304" (استجابات غير مخزّنة مؤقتًا وغير ناجحة) grep -Ev " (200|304) " /var/log/nginx/access.log | tail -100
نصيحة الإنتاج — استخدم grep -F للسلاسل الحرفية: عندما يحتوي نمط البحث على حروف خاصة في التعبيرات النظامية (.، *، [، $)، استخدم grep -F (سلسلة ثابتة) لتخطي تفسير التعبيرات النظامية كليًا. البحث عن 10.0.1.5 بـ grep العادي يطابق أي حرف في مواضع النقاط. grep -F "10.0.1.5" يطابق السلسلة الحرفية بالضبط. هذا مهم عند البحث في السجلات عن عناوين IP أو أسماء فئات تتبع المكدس أو أي سلسلة تبدو كتعبير نظامي.

sed — تحرير الدفق للتحويل

sed (محرر الدفق) يقرأ المدخلات سطرًا بسطر، ويُطبّق برنامج أوامر تحرير، ويكتب إلى المخرجات القياسية. لا يعدّل الملفات في مكانها أبدًا ما لم تمرر -i. أمر sed الأكثر استخدامًا هو s/نمط/استبدال/أعلام — الاستبدال. فهم sed يعني فهم هذا الأمر الواحد بعمق.

أعلام الاستبدال المهمة:

  • g — استبدال جميع التكرارات في السطر (وليس الأول فقط)
  • i — مطابقة غير حساسة لحالة الأحرف (GNU sed)
  • p — طباعة السطر بعد الاستبدال (مفيد مع -n لطباعة الأسطر المعدّلة فقط)
  • 2، 3 — استبدال التكرار رقم N فقط في السطر
# استبدال "localhost" بـ "prod-db.internal" في ملف إعداد (طباعة النتيجة) sed 's/localhost/prod-db.internal/g' database.conf # تعديل ملف في مكانه (العلَم -i)؛ GNU sed يتطلب سلسلة فارغة لعدم النسخ الاحتياطي sed -i 's/DEBUG=true/DEBUG=false/g' /etc/app/config.env # تعديل في مكانه مع نسخة احتياطية (آمن لتغييرات إعدادات الإنتاج) sed -i.bak 's/DEBUG=true/DEBUG=false/g' /etc/app/config.env # ينشئ config.env.bak قبل تعديل config.env # حذف جميع الأسطر الفارغة من ملف sed '/^$/d' nginx.conf # حذف الأسطر من 1 إلى 5 (مثلًا إزالة رأس ملف) sed '1,5d' report.csv # طباعة الأسطر من 10 إلى 20 فقط sed -n '10,20p' /var/log/app/app.log # إزالة رموز الألوان ANSI من ملفات السجل قبل تخزينها sed 's/\x1b\[[0-9;]*m//g' colored.log > clean.log # إلغاء تعليق الأسطر التي تبدأ بـ "# PROD:" (إزالة "# PROD:" البادئة) sed 's/^# PROD: //' config.template > config.prod # أوامر sed متعددة: حذف التعليقات والأسطر الفارغة في تمريرة واحدة sed -e '/^#/d' -e '/^$/d' nginx.conf.template
فخ الإنتاج — اختلاف sed -i بين GNU وBSD: على Linux، sed -i '' يفشل (السلسلة الفارغة بعد -i تُعامَل كوسيط التالي). على macOS (BSD sed)، sed -i '' هو التعديل الصحيح في المكان دون نسخة احتياطية. لكتابة سكريبتات محمولة، استخدم sed -i.bak (ينشئ دائمًا نسخة احتياطية) أو استخدم perl -pi -e بدلًا من ذلك، الذي يتصرف باتساق. في Dockerfiles وخطوط CI التي تستهدف حاويات Linux، نادرًا ما تواجه هذا الفرق، لكنه سيصطادك حتمًا حين يشغّل أحد زملائك سكريبتك على Mac.

awk — معالجة البيانات الموجّهة بالحقول

awk لغة برمجة كاملة مصمّمة حول مفهوم السجلات والحقول. بشكل افتراضي، تقسّم كل سطر مدخلات (سجل) على المسافات البيضاء إلى حقول ($1، $2، ...، $NF للحقل الأخير، $0 للسطر بأكمله). تُنفّذ برنامج نمط-إجراء على كل سجل. الشكل المعياري هو awk '/نمط/ { إجراء }'.

المتغيرات المدمجة التي تظهر باستمرار في السكريبتات الحقيقية:

  • NR — رقم السطر (السجل) الحالي عبر جميع الملفات
  • NF — عدد الحقول في السجل الحالي
  • FS — فاصل الحقول (الافتراضي: مسافات بيضاء؛ يُضبط بـ -F)
  • OFS — فاصل حقول المخرجات (الافتراضي: مسافة)
  • $0 — السطر الحالي بأكمله
  • $1 ... $NF — الحقول الفردية
  • BEGIN { } — يُنفَّذ مرة واحدة قبل قراءة أي مدخلات
  • END { } — يُنفَّذ مرة واحدة بعد استهلاك جميع المدخلات
# طباعة العمود الخامس (رمز حالة HTTP) من سجل وصول nginx awk '{print $9}' /var/log/nginx/access.log # طباعة عنوان IP ورمز الحالة لجميع الاستجابات غير 200 awk '$9 != 200 {print $1, $9}' /var/log/nginx/access.log # جمع البايتات المُرسَلة (الحقل 10) لجميع الطلبات awk '{total += $10} END {print "Total bytes:", total}' /var/log/nginx/access.log # حساب تكرارات كل رمز حالة HTTP awk '{counts[$9]++} END {for (code in counts) print code, counts[code]}' \ /var/log/nginx/access.log | sort -k1,1n # تحليل /etc/passwd المفصول بنقطتين وطباعة اسم المستخدم + Shell awk -F: '{print $1, $NF}' /etc/passwd # طباعة الأسطر التي يتجاوز فيها الحقل 5 (وقت الاستجابة بالملي ثانية) 1000 awk -F',' '$5 > 1000 {print $0}' slow_requests.csv # طباعة سطر الرأس بالإضافة إلى جميع أسطر الحالة 5xx awk 'NR==1 || /,5[0-9][0-9],/' report.csv # حساب متوسط وقت الاستجابة من CSV مع الوقت في الحقل 4 awk -F',' 'NR > 1 {sum += $4; count++} END {printf "Avg: %.2f ms\n", sum/count}' metrics.csv

تأليف الأدوات الثلاث: خطوط أنابيب للعالم الحقيقي

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

grep | sed | awk pipeline data flow Log File all lines pipe grep SELECT lines pattern match only 5xx errors pipe sed TRANSFORM text strip timestamps normalise format pipe awk COMPUTE & report count per IP sum, average Report stdout كل أداة تعالج مخرجات المرحلة السابقة فقط لا ملفات وسيطة، لا انتظار لتحميل مجموعة البيانات بالكامل
خط أنابيب grep | sed | awk: تتدفق الأسطر من اليسار إلى اليمين، كل أداة تُنقّح الدفق أكثر.
# السيناريو 1: عدّ عناوين IP الفريدة التي أنتجت أخطاء HTTP 500 اليوم # grep يختار أسطر 500، awk يستخرج عنوان IP، sort+uniq يعدّها grep " 500 " /var/log/nginx/access.log \ | awk '{print $1}' \ | sort | uniq -c | sort -rn \ | head -20 # السيناريو 2: استخراج استعلامات قاعدة البيانات البطيئة من سجل MySQL البطيء # grep يجد أسطر "Query_time"، awk يُصفّي التي تتجاوز 2 ثانية، # sed يُنظّف الشكل لتقرير CSV grep "Query_time" /var/log/mysql/slow.log \ | awk '$3 > 2.0 {print $3, $0}' \ | sort -rn \ | head -50 # السيناريو 3: تلخيص استخدام القرص لكل مجلد عبر خوادم متعددة echo "جاري التجميع..." for server in web01 web02 web03; do ssh "$server" "du -sh /var/log/app/* 2>/dev/null" done \ | sed 's/\t/ /' \ | awk '{sizes[$2] += $1} END {for (d in sizes) print sizes[d], d}' \ | sort -rn # السيناريو 4: تنقية الحقول الحساسة قبل إرسال السجلات إلى SIEM # استبدال أرقام بطاقات الائتمان (نمط 16 رقمًا بسيط) بـ [REDACTED] grep -v "^#" app.log \ | sed -E 's/[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{4}[- ]?[0-9]{4}/[REDACTED]/g' \ > sanitised.log

اعتبارات الأداء على نطاق واسع

عند معالجة ملفات سجل بحجم جيجابايتات — وهو أمر شائع في الإنتاج — فإن اختيار الأداة وترتيبها له تأثيرات أداء حقيقية:

  • ضع grep أولًا في خط الأنابيب. يرفض الأسطر مبكرًا، مما يقلل البيانات التي يجب على sed وawk معالجتها. تصفية 100 مليون سطر إلى 10,000 سطر قبل تسليمها لـ awk أسرع بمراتب من تغذية كل الـ 100 مليون لـ awk.
  • استخدم grep -F للسلاسل الثابتة. مطابقة التعبيرات النظامية لها تكلفة؛ مطابقة السلاسل البسيطة (خوارزمية Boyer-Moore) أسرع بشكل ملحوظ عندما لا تحتاج تعبيرات نظامية.
  • فضّل awk على حلقة shell لعمليات الأعمدة الحسابية. حلقة shell تعالج سطرًا واحدًا لكل تكرار وتُفرز عملية فرعية لكل سطر؛ awk يعالج ملايين الأسطر في استدعاء عملية واحدة.
  • بالنسبة للملفات الكبيرة جدًا، فكّر في LC_ALL=C. الإضافة البادئة LC_ALL=C تُعطّل معالجة الحروف متعددة البايت ويمكن أن تجعل grep وawk أسرع بمعدل 3-5 أضعاف على السجلات ذات السيادة ASCII.
# أسرع grep ممكن على ملف سجل ASCII كبير LC_ALL=C grep -F "ERROR" /var/log/app/app.log | wc -l # معالجة ملف سجل مضغوط بحجم 10 جيجابايت دون فك الضغط إلى القرص zcat /var/log/nginx/access.log.gz \ | LC_ALL=C grep " 500 " \ | awk '{print $1}' \ | sort | uniq -c | sort -rn | head -10
ممارسة احترافية — احفظ خط الأنابيب كسكريبت: عندما تُصمّم خط أنابيب متعدد المراحل ستشغّله أنت أو فريقك بشكل متكرر (تقرير يومي، قالب تحقيق حوادث، تحليل تكاليف)، احفظه كسكريبت مُسمًّى في مجلد scripts/. خطوط الأنابيب التي تعيش في تاريخ شِلك فقط هي معرفة تشغيلية تختفي عندما تغادر الفريق. وثّق صيغة المدخلات والمخرجات المتوقعة والتبعيات الخارجية في أعلى الملف.

أنماط الفشل الشائعة

  • grep يُعيد كود خروج 1 (لا تطابق) ويوقف خط أنابيب مع set -e. في السكريبتات التي تستخدم set -e، grep الذي لا يجد شيئًا يخرج بكود 1، مما يُجهض السكريبت. احمِ نفسك بـ grep ... || true عندما تكون نتيجة الصفر تطابقات مقبولة.
  • sed في المكان على رابط رمزي يتبع الرابط وليس الرابط نفسه. عندما يكون ملف الإعداد رابطًا رمزيًا (شائع في البيئات المُدارة بـ Ansible)، sed -i يستبدل هدف الرابط. تحقق من النتيجة بـ ls -la بعد التعديل.
  • ترقيم حقول awk يبدأ من 1 وليس 0. $0 هو السطر بأكمله؛ $1 هو الحقل الأول. المهندسون القادمون من Python أو Go يُسنّدون رقم الحقل الخاطئ ويحصلون بصمت على مخرجات غير صحيحة — دائمًا تحقق بعينة صغيرة أولًا.
  • التعبيرات النظامية الحساسة للإعداد المحلي في awk. فئات الحروف مثل [[:alpha:]] تطابق بشكل مختلف حسب الإعداد المحلي. اضبط LC_ALL=C للمطابقة المتوقعة المقتصرة على ASCII في سكريبتات الإنتاج.

مع grep وsed وawk في صندوق أدواتك — وفهم كيفية تأليفها — يمكنك الإجابة في ثوانٍ على أسئلة كانت تتطلب كتابة سكريبت Python أو تحميل البيانات في قاعدة بيانات أو انتظار تحديث لوحة تحكم BI. على نطاق واسع، تلك السرعة هي الفرق بين حادثة تستمر 10 دقائق وتشخيص يستغرق 10 ثوانٍ.