أتمتة لينكس بسكربتات Bash

كل مهندس لينكس يصل إلى لحظة يكتشف فيها أنه يكرّر نفس مجموعة الأوامر يدويًا كل يوم: نسخ احتياطي، تنظيف سجلّات، فحص حالة خدمة. هنا يأتي دور سكربتات Bash لتحويل هذه المهام المتكرّرة إلى أوامر تُنفّذ بنفسها. في هذا المقال سنبني فهمًا عمليًا لكتابة سكربتات Bash آمنة وموثوقة، من أبسط الأساسيات وصولًا إلى سكربت نسخ احتياطي كامل مجدول بـ cron.

أساسيات السكربت: الـ shebang والصلاحيات

أي سكربت Bash يبدأ بسطر يُسمّى shebang يخبر النظام بأي مفسّر يشغّل الملف. الصيغة المفضّلة لقابلية النقل هي:

#!/usr/bin/env bash

نستخدم env بدل المسار المباشر /bin/bash لأنه يبحث عن bash في مسار النظام، فيعمل السكربت حتى لو كان bash في مكان مختلف على توزيعات أخرى. بعد كتابة السكربت، يجب منحه صلاحية التنفيذ وإلا لن يعمل:

chmod +x myscript.sh
./myscript.sh

المتغيّرات والـ command substitution

المتغيّرات في Bash تُعرّف بدون مسافات حول علامة المساواة، وتُستدعى بإضافة $. ومن أقوى الميزات هي command substitution التي تخزّن مخرجات أمر داخل متغيّر باستخدام $(...):

name="World"
echo "Hello, $name"

# تخزين ناتج أمر داخل متغيّر
current_date=$(date +%F)
file_count=$(ls -1 | wc -l)
echo "Today is $current_date and there are $file_count items here"

القاعدة الذهبية هنا: ضع المتغيّرات دائمًا بين علامتي اقتباس مزدوجتين "$name". هذا يحميك من مشكلة word splitting التي سنشرحها في الأخطاء الشائعة.

الشروط: if وtest و[[ ]]

الشروط تتيح لسكربتك اتخاذ قرارات. الصيغة الحديثة المفضّلة في Bash هي القوسان المزدوجان [[ ]] لأنها أكثر أمانًا من القوس الواحد القديم:

backup_dir="/var/backups"

if [[ -d "$backup_dir" ]]; then
    echo "Directory exists"
else
    echo "Directory missing, creating it"
    mkdir -p "$backup_dir"
fi

العلامة -d تختبر وجود مجلّد، و-f تختبر وجود ملف، و-z تختبر إن كان النص فارغًا. يمكنك أيضًا مقارنة الأرقام بـ -eq و-gt و-lt، والنصوص بـ == و!=.

الحلقات: for وwhile

الحلقات تكرّر تنفيذ مجموعة أوامر. حلقة for ممتازة للمرور على قائمة عناصر، وwhile للاستمرار طالما تحقّق شرط:

# المرور على كل ملفات .log في مجلّد
for logfile in /var/log/*.log; do
    echo "Found log: $logfile"
done

# حلقة while مع عدّاد
count=1
while [[ $count -le 5 ]]; do
    echo "Iteration $count"
    count=$((count + 1))
done

لاحظ $((count + 1)) وهي صيغة الحساب الرياضي في Bash. حلقة for الأولى تستفيد من الـ globbing حيث يوسّع Bash النمط *.log إلى كل الملفات المطابقة تلقائيًا.

الدوال

عندما يكبر السكربت، نظّمه في دوال لإعادة استخدام المنطق وتجنّب التكرار:

log_message() {
    local level="$1"
    local message="$2"
    echo "[$(date +'%F %T')] [$level] $message"
}

log_message "INFO" "Backup started"
log_message "ERROR" "Disk is full"

الكلمة local تجعل المتغيّر محصورًا داخل الدالة فقط، وهو أمر مهمّ لتجنّب تلويث المتغيّرات العامّة. المعاملات تُستقبل بالترتيب عبر $1 و$2 وهكذا.

المدخلات والـ exit codes

كل أمر في لينكس يعيد رمز خروج (exit code): القيمة 0 تعني نجاحًا، وأي قيمة أخرى تعني فشلًا. يمكنك فحص آخر رمز عبر المتغيّر الخاص $?، وإنهاء سكربتك برمز محدّد عبر exit:

if ! command -v rsync &> /dev/null; then
    echo "Error: rsync is not installed" >&2
    exit 1
fi

هنا نتحقّق إن كانت أداة rsync مثبّتة، وإن لم تكن نطبع رسالة خطأ على مجرى الأخطاء >&2 ونخرج برمز 1 للدلالة على الفشل. هذا السلوك يسمح لأدوات أخرى أو لـ cron بمعرفة أن السكربت فشل.

كتابة سكربت آمن: set -euo pipefail

أهمّ سطر في أي سكربت احترافي يأتي مباشرة بعد الـ shebang:

#!/usr/bin/env bash
set -euo pipefail

هذا السطر يفعّل ثلاثة حمايات: -e يوقف السكربت فورًا عند فشل أي أمر بدل الاستمرار بصمت، و-u يعتبر استخدام متغيّر غير معرّف خطأ بدل تجاهله، و-o pipefail يجعل سلسلة الأنابيب (pipe) تفشل إذا فشل أي جزء منها وليس فقط الأخير. هذه الإعدادات الثلاثة تحوّل سكربتك من هشّ إلى موثوق.

مثال عملي كامل: سكربت نسخ احتياطي

الآن لنجمع كل ما تعلّمناه في سكربت نسخ احتياطي حقيقي لمجلّد، يضيف تاريخًا لاسم الأرشيف ويحذف النسخ الأقدم من 7 أيام:

#!/usr/bin/env bash
set -euo pipefail

SOURCE_DIR="/var/www/myapp"
BACKUP_DIR="/var/backups/myapp"
TIMESTAMP=$(date +'%Y-%m-%d_%H-%M-%S')
ARCHIVE="$BACKUP_DIR/backup_$TIMESTAMP.tar.gz"

# التأكّد من وجود مجلّد النسخ الاحتياطي
mkdir -p "$BACKUP_DIR"

# إنشاء الأرشيف المضغوط
tar -czf "$ARCHIVE" -C "$SOURCE_DIR" .

echo "Backup created: $ARCHIVE"

# حذف النسخ الأقدم من 7 أيام
find "$BACKUP_DIR" -name "backup_*.tar.gz" -type f -mtime +7 -delete

echo "Old backups cleaned up"

لاحظ كيف تُحاط كل المسارات بعلامات اقتباس، وكيف نستخدم $(date ...) لإنشاء اسم فريد لكل نسخة. أمر tar مع -C يدخل المجلّد المصدر أولًا لتجنّب تضمين المسار الكامل في الأرشيف، وfind مع -mtime +7 يحذف الملفات الأقدم من سبعة أيام تلقائيًا.

الجدولة عبر cron

لتشغيل السكربت تلقائيًا، نستخدم cron. افتح جدول المهام بالأمر:

crontab -e

ثم أضف سطرًا لتشغيل السكربت يوميًا في الساعة الثانية صباحًا. الحقول الخمسة تعني بالترتيب: الدقيقة، الساعة، اليوم من الشهر، الشهر، اليوم من الأسبوع:

# تشغيل النسخ الاحتياطي يوميًا الساعة 2:00 صباحًا
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

لاحظ أننا نستخدم المسار الكامل للسكربت، ونوجّه المخرجات والأخطاء إلى ملف سجلّ عبر >> ... 2>&1 حتى نتمكّن من مراجعة ما حدث لاحقًا، لأن cron لا يعرض المخرجات على الشاشة.

أخطاء شائعة

  • عدم اقتباس المتغيّرات: إذا كتبت rm $file وكان المسار يحتوي مسافة مثل "my file.txt"، سيقسمه Bash إلى كلمتين (word splitting) ويحاول حذف ملفين خاطئين. اكتب دائمًا rm "$file".
  • نسيان set -e: يفشل أمر في منتصف السكربت لكنه يكمل التنفيذ بصمت متجاهلًا الخطأ، فتظنّ أن كل شيء نجح بينما النسخة الاحتياطية فارغة.
  • المسارات النسبية في cron: يعمل السكربت يدويًا لكنه يفشل تحت cron لأن cron يشتغل ببيئة محدودة وPATH مختلف. استخدم دائمًا مسارات مطلقة كاملة للأوامر والملفات داخل السكربتات المجدولة.
  • نسيان chmod +x: تكتب سكربتًا ممتازًا ثم تحصل على رسالة Permission denied لأنك لم تمنحه صلاحية التنفيذ.

الخطوة التالية

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

اترك تعليقاً