Linux: سكريبت Bash المتقدم

السكريبتات الأساسية تتعامل مع المهام البسيطة، لكن سكريبتات مستوى الإنتاج تحتاج كودًا أكثر متانة — دوال لتقسيم المنطق، ومعالجة أخطاء لمنع "الفشل الصامت"، وتحليل معاملات لجعل السكريبتات أكثر قابلية للاستخدام.

📋 المتطلبات المسبقة: يجب أن تعرف بالفعل

1. ما ستتعلمه


2. قصة سكريبت شياو مينغ الذي جمح على الخادم

(1) نقطة الألم: السكريبت يستمر بعد الأخطاء

كتب شياو مينغ سكريبت نشر:

BASH
#!/bin/bash
cd /var/www/myapp
git pull origin main
npm install            # ❌ هذا فشل
npm run build          # لكن السكريبت استمر
pm2 restart app        # أعاد التشغيل بكود قديم وبناء معطوب

(2) إضافة سطر واحد تجعله آمنًا

قال بوب: "أضف set -e في الأعلى — يتوقف عند أي خطأ."

TEXT
#!/bin/bash
set -e                 # توقف عند الخطأ
cd /var/www/myapp
git pull origin main
npm install
npm run build
pm2 restart app

(3) الفائدة: السلامة أولًا

بفضل set -e، إذا فشل npm install، يخرج السكريبت فورًا بدلًا من إعادة تشغيل الخدمة بكود معطوب.


3. نقاط المعرفة

(1) الدوال

TEXT
# تعريف دالة
greet() {
    echo "Hello, $1!"
    echo "Today is $(date)"
}

# صياغة بديلة
function greet {
    echo "Hello, $1!"
}

# استدعاؤها
greet "Alice"

# المتغيرات المحلية
myfunc() {
    local x=10        # متغير محلي، لا يؤثر على النطاق الخارجي
    echo "Inside: $x"
}
x=5
myfunc                # الإخراج: Inside: 10
echo "Outside: $x"    # الإخراج: Outside: 5

(2) المصفوفات

TEXT
# مصفوفة مفهرسة
fruits=("apple" "banana" "orange")
fruits[3]="grape"     # إضافة عنصر

echo ${fruits[0]}     # apple
echo ${fruits[@]}     # كل العناصر
echo ${#fruits[@]}    # طول المصفوفة

# التكرار على المصفوفة
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

# مصفوفة ترابطية (مثل قاموس)
declare -A config
config[host]="localhost"
config[port]="8080"
config[user]="admin"

echo ${config[host]}  # localhost

(3) معالجة الأخطاء

TEXT
# set -e: خروج عند الخطأ
set -e

# set -u: خطأ عند متغير غير معرّف
set -u

# set -o pipefail: فشل الأنبوبة إذا فشل أي أمر
set -o pipefail

# المزيج الشائع
set -euo pipefail

> 💡 نصيحة: `set -euo pipefail` هو "الثلاثي الآمن" لسكريبتات الإنتاج — `-e` يتوقف عند الخطأ، و`-u` يلتقط المتغيرات غير المعرفة، و`-o pipefail` يجعل أخطاء الأنابيب تنتشر. كل السكريبتات غير البسيطة يجب أن تتضمن هذا السطر.

# trap: تنفيذ تنظيف عند الإشارة أو الخروج
trap 'echo "Script interrupted"; exit 1' INT TERM
trap 'rm -f /tmp/tempfile; echo "Cleanup done"' EXIT

> 💡 نصيحة: `trap` يمكنه تنفيذ تنظيف تلقائي عند خروج السكريبت — سواء انتهى عاديًا أو قُوطع بـCtrl+C، يُطلق `trap EXIT`. استخدامات شائعة: حذف ملفات مؤقتة، تحرير أقفال ملفات، استعادة إعدادات. اكتب `trap 'rm -f $TMPFILE' EXIT` في الأعلى ولن تحتاج تنظيف يدوي عند كل نقطة خروج.

# معالج أخطاء مخصص
error_handler() {
    echo "❌ Error on line $1"
    echo "Command: $2"
    exit 1
}
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR

(4) getopts — تحليل المعاملات

TEXT
# صياغة getopts
while getopts "f:o:hv" opt; do
    case $opt in
        f) INPUT_FILE="$OPTARG" ;;   # -f filename
        o) OUTPUT_DIR="$OPTARG" ;;   # -o output
        h) show_help; exit 0 ;;      # -h مساعدة
        v) VERBOSE=true ;;           # -v إخراج مفصّل
        *) echo "Unknown option"; exit 1 ;;
    esac
done

> ℹ️ ملاحظة: `getopts` هو محلل المعاملات المدمج في bash — في سلسلة الخيارات `"f:o:hv"`، نقطتان `:` تعني أن الخيار يتطلب معاملًا (مثل `-f filename`)، بدون نقطتين يعني أنه خيار علم (مثل `-v`). لا يدعم الخيارات الطويلة (مثل `--help`)؛ لتلك، استخدم أمر `getopt` الخارجي أو التحليل اليدوي.

(5) تقنيات التصحيح

TEXT
# وضع التصحيح: عرض كل أمر أثناء تنفيذه
set -x

# إيقاف التصحيح
set +x

# تفعيل التصحيح لقسم محدد
echo "Normal execution"
set -x
echo "This section is traced"
for i in 1 2 3; do echo "$i"; done
set +x
echo "Back to normal"

# أو التصحيح عند تشغيل السكريبت
# bash -x script.sh

(6) ▶ مثال: تغليف الدوال

TEXT
#!/bin/bash
# functions-demo.sh - أمثلة على الدوال

log() {
    local level="${1:-INFO}"
    local message="$2"
    echo "[$(date '+%H:%M:%S')] [$level] $message"
}

die() {
    log "ERROR" "$1"
    exit 1
}

check_dependency() {
    if ! command -v "$1" &> /dev/null; then
        die "Required command '$1' not found"
    fi
}

# استخدام الدوال
log "INFO" "Starting dependency check..."
check_dependency "git"
check_dependency "node"
log "INFO" "Dependency check passed"

(7) ▶ مثال: عمليات المصفوفات

TEXT
#!/bin/bash
# array-demo.sh - أمثلة على المصفوفات

# قائمة الخوادم
servers=("web01" "web02" "db01" "cache01")

echo "Server count: ${#servers[@]}"

echo "--- Iterating servers ---"
for server in "${servers[@]}"; do
    echo "Checking: $server"
done

echo "--- Associative array example ---"
declare -A disk_usage
disk_usage[/]="45%"
disk_usage[/home]="62%"
disk_usage[/var]="78%"

for mount in "${!disk_usage[@]}"; do
    echo "$mount: ${disk_usage[$mount]}"
done

الإخراج:

TEXT
[10:23:01] [INFO] Starting dependency check...
[10:23:01] [INFO] Dependency check passed
Server count: 4
--- Iterating servers ---
Checking: web01
Checking: web02
Checking: db01
Checking: cache01
--- Associative array example ---
/: 45%
/home: 62%
/var: 78%

(8) ▶ مثال: معالجة الأخطاء بset -e / trap

BASH
#!/bin/bash
# safe-script.sh - سكريبت متين

set -euo pipefail

cleanup() {
    echo "Cleaning temporary files..."
    rm -f /tmp/temp_*.txt
    echo "Done"
}

error_handler() {
    echo "❌ Error occurred on line $1"
    cleanup
    exit 1
}

trap error_handler ERR
trap cleanup EXIT

echo "Starting execution..."
mkdir -p /tmp/test
touch /tmp/test/file.txt
cat /tmp/test/file.txt
# If cat fails on a non-existent file, the script stops and triggers ERR trap

الإخراج:

TEXT
Starting execution...
Cleaning temporary files...
Done

(9) ▶ مثال: تحليل المعاملات بgetopts

BASH
#!/bin/bash
# backup-tool.sh - أداة نسخ احتياطي مع تحليل معاملات

usage() {
    echo "Usage: $0 -s <source_dir> -d <dest_dir> [-c] [-v]"
    echo "  -s  Source directory to back up"
    echo "  -d  Backup destination directory"
    echo "  -c  Compress backup"
    echo "  -v  Verbose output"
    echo "  -h  Show help"
    exit 0
}

# قيم افتراضية
COMPRESS=false
VERBOSE=false

while getopts "s:d:cvh" opt; do
    case $opt in
        s) SOURCE="$OPTARG" ;;
        d) DEST="$OPTARG" ;;
        c) COMPRESS=true ;;
        v) VERBOSE=true ;;
        h) usage ;;
        *) usage ;;
    esac
done

# التحقق من المعاملات المطلوبة
if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
    echo "Error: Must specify -s and -d"
    usage
fi

if [ ! -d "$SOURCE" ]; then
    echo "Error: Source directory '$SOURCE' does not exist"
    exit 1
fi

# تنفيذ النسخ الاحتياطي
mkdir -p "$DEST"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_${TIMESTAMP}"

if [ "$COMPRESS" = true ]; then
    tar -czf "${DEST}/${BACKUP_NAME}.tar.gz" "$SOURCE"
    echo "✅ Compressed backup complete: ${DEST}/${BACKUP_NAME}.tar.gz"
else
    cp -r "$SOURCE" "${DEST}/${BACKUP_NAME}"
    echo "✅ Backup complete: ${DEST}/${BACKUP_NAME}"
fi

if [ "$VERBOSE" = true ]; then
    echo "Source: $SOURCE"
    echo "Destination: $DEST"
    echo "Size: $(du -sh "$SOURCE" | cut -f1)"
fi

الإخراج:

TEXT
Usage: ./backup-tool.sh -s <source_dir> -d <dest_dir> [-c] [-v]
  -s  Source directory to back up
  -d  Backup destination directory
  -c  Compress backup
  -v  Verbose output
  -h  Show help
✅ Compressed backup complete: /backup/backup_20260707_102345.tar.gz
Source: /var/www/myapp
Destination: /backup
Size: 12M

(10) ▶ مثال: تقنيات التصحيح

TEXT
#!/bin/bash
# debug-demo.sh - مثال على التصحيح

# فعّل التتبع فقط حيث تحتاج التصحيح
echo "Script started PID: $$"

for i in 1 2 3; do
    set -x              # بدء التتبع
    echo "Loop iteration $i"
    result=$((i * 10))
    echo "Result: $result"
    set +x              # إيقاف التتبع
done

echo "Script execution complete"

(11) ▶ مثال شامل: سكريبت نشر بمستوى الإنتاج

BASH
#!/bin/bash
# deploy.sh - سكريبت نشر بمستوى الإنتاج

set -euo pipefail

APP_NAME="webapp"
APP_DIR="/var/www/$APP_NAME"
BACKUP_DIR="/backup/$APP_NAME"
LOG_FILE="/var/log/deploy.log"

# إخراج ملون
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'

log() {
    local level="$1"
    local msg="$2"
    echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${msg}" | tee -a "$LOG_FILE"
}

info()  { log "INFO" "$1"; }
success() { log "INFO" "${GREEN}$1${NC}"; }
error() { log "ERROR" "${RED}$1${NC}"; }

cleanup() {
    info "Cleaning deployment temporary files..."
}

error_handler() {
    error "Deployment failed at line $1"
    error "Command: $BASH_COMMAND"
    cleanup
    exit 1
}

usage() {
    echo "Usage: $0 [options]"
    echo "  -b   Specify Git branch (default: main)"
    echo "  -e   Specify environment (dev/staging/prod)"
    echo "  -n   Skip backup"
    echo "  -h   Show help"
    exit 0
}

trap error_handler ERR
trap cleanup EXIT

# --- تحليل المعاملات ---
BRANCH="main"
ENV="prod"
SKIP_BACKUP=false

while getopts "b:e:nh" opt; do
    case $opt in
        b) BRANCH="$OPTARG" ;;
        e) ENV="$OPTARG" ;;
        n) SKIP_BACKUP=true ;;
        h) usage ;;
        *) usage ;;
    esac
done

# --- فحص البيئة ---
info "Starting deployment of $APP_NAME (branch: $BRANCH, environment: $ENV)"

if [ ! -d "$APP_DIR" ]; then
    error "Application directory $APP_DIR does not exist"
    exit 1
fi

# --- النسخ الاحتياطي ---
if [ "$SKIP_BACKUP" = false ]; then
    info "Backing up current version..."
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    mkdir -p "$BACKUP_DIR"
    tar -czf "${BACKUP_DIR}/${TIMESTAMP}.tar.gz" \
        --exclude=node_modules \
        --exclude=.git \
        "$APP_DIR"
    success "Backup complete: ${BACKUP_DIR}/${TIMESTAMP}.tar.gz"
fi

# --- النشر ---
cd "$APP_DIR"

info "Pulling latest code..."
git fetch origin
git checkout "$BRANCH"
git pull origin "$BRANCH"

info "Installing dependencies..."
npm ci --production

info "Building application..."
if [ "$ENV" = "production" ]; then
    npm run build:prod
else
    npm run build
fi

info "Restarting service..."
pm2 restart "$APP_NAME" || pm2 start npm --name "$APP_NAME" -- start

# --- فحص الصحة ---
info "Running health check..."
sleep 3
if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health | grep -q "200"; then
    success "✅ Deployment successful! Health check passed"
else
    error "❌ Health check failed, please investigate manually"
    exit 1
fi

❓ أسئلة شائعة

س: ما الفرق بين الدوال والأسماء المستعارة (aliases)؟ ج: الدوال أقوى من الأسماء المستعارة: تدعم المعاملات، والمتغيرات المحلية، وقيم الإرجاع، وإعادة الاستخدام. الأسماء المستعارة مجرد استبدال بسيط للأوامر. استخدم الدوال للمنطق المعقد، والأسماء المستعارة للاختصارات البسيطة.

س: متى لا يعمل set -e؟ ج: set -e لا يُطلق في هذه الحالات: 1) في تعبيرات if/while/until الشرطية. 2) بعد && أو ||. 3) في الأنابيب (أضف set -o pipefail). 4) عندما يُنفى أمر بـ!.

س: أي إشارات يمكن لـtrap التقاطها؟ ج: الشائعة: EXIT (خروج السكريبت)، وERR (فشل أمر)، وINT (Ctrl+C)، وTERM (إشارة kill الافتراضية). أخرى تشمل HUP وQUIT وUSR1 وUSR2.

س: ما الفرق بين getopts وgetopt؟ ج: getopts مدمج في bash، أبسط، لا يدعم الخيارات الطويلة (مثل --help). getopt أمر خارجي (من حزمة util-linux) يدعم مزج الخيارات الطويلة والقصيرة. فضّل getopts للتوافق.

س: كيف أنشئ ملفات مؤقتة في سكريبت؟ ج: استخدم mktemp: tempfile=$(mktemp) ينشئ ملفًا مؤقتًا، وtempdir=$(mktemp -d) ينشئ دليلًا مؤقتًا. الملفات المؤقتة تُنشأ عادةً في /tmp؛ تذكّر حذفها عند الانتهاء.


📖 ملخص


📝 تمارين

  1. أساسي (⭐): أعِد هيكلة سكريبت النسخ الاحتياطي من الدرس 20 باستخدام الدوال (log/die/check_dependency إلخ)، وأضف set -euo pipefail ومعالجة أخطاء trap
  2. متوسط (⭐⭐): أضف معاملات -s (دليل المصدر) و-d (دليل الوجهة) باستخدام getopts، وأنشئ مصفوفة ترابطية لتخزين الإعدادات، وكرّر لإخراج كل عناصر الإعدادات
  3. متقدم (⭐⭐⭐): استخدم mktemp لإنشاء دليل مؤقت يُنظَّف تلقائيًا عند الانتهاء، واكتب سكريبت نشر بمستوى إنتاج — مع تحليل معاملات، وتسجيل ملون، ومعالجة أخطاء، وفحص صحة
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%