Linux: سكريبت Bash المتقدم
السكريبتات الأساسية تتعامل مع المهام البسيطة، لكن سكريبتات مستوى الإنتاج تحتاج كودًا أكثر متانة — دوال لتقسيم المنطق، ومعالجة أخطاء لمنع "الفشل الصامت"، وتحليل معاملات لجعل السكريبتات أكثر قابلية للاستخدام.
📋 المتطلبات المسبقة: يجب أن تعرف بالفعل
- الدرس 20: أساسيات سكريبت Bash
1. ما ستتعلمه
- تعريف الدوال وقيم الإرجاع
- المصفوفات المفهرسة والترابطية
- معالجة الأخطاء بset -e وtrap
- معاملات سطر الأوامر بgetopts
- تقنيات التصحيح بset -x
2. قصة سكريبت شياو مينغ الذي جمح على الخادم
(1) نقطة الألم: السكريبت يستمر بعد الأخطاء
كتب شياو مينغ سكريبت نشر:
#!/bin/bash
cd /var/www/myapp
git pull origin main
npm install # ❌ هذا فشل
npm run build # لكن السكريبت استمر
pm2 restart app # أعاد التشغيل بكود قديم وبناء معطوب
(2) إضافة سطر واحد تجعله آمنًا
قال بوب: "أضف set -e في الأعلى — يتوقف عند أي خطأ."
#!/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) الدوال
# تعريف دالة
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) المصفوفات
# مصفوفة مفهرسة
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) معالجة الأخطاء
# 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 — تحليل المعاملات
# صياغة 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) تقنيات التصحيح
# وضع التصحيح: عرض كل أمر أثناء تنفيذه
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) ▶ مثال: تغليف الدوال
#!/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) ▶ مثال: عمليات المصفوفات
#!/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
#!/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
الإخراج:
TEXTStarting execution... Cleaning temporary files... Done
(9) ▶ مثال: تحليل المعاملات بgetopts
#!/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
الإخراج:
TEXTUsage: ./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) ▶ مثال: تقنيات التصحيح
#!/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) ▶ مثال شامل: سكريبت نشر بمستوى الإنتاج
#!/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؛ تذكّر حذفها عند الانتهاء.
📖 ملخص
- الدوال
func() { local vars; return N; }تغلف المنطق - المصفوفات:
${arr[@]}تكرار، و${#arr[@]}طول، وdeclare -Aمصفوفات ترابطية set -euo pipefailيجعل السكريبتات متينة، وtrap ERR EXITيلتقط الأخطاء وينظّفgetoptsيحلل معاملات سطر الأوامرset -x/+xتصحيح، وbash -x script.shتشغيل في وضع التصحيح
📝 تمارين
- أساسي (⭐): أعِد هيكلة سكريبت النسخ الاحتياطي من الدرس 20 باستخدام الدوال (log/die/check_dependency إلخ)، وأضف
set -euo pipefailومعالجة أخطاءtrap - متوسط (⭐⭐): أضف معاملات
-s(دليل المصدر) و-d(دليل الوجهة) باستخدامgetopts، وأنشئ مصفوفة ترابطية لتخزين الإعدادات، وكرّر لإخراج كل عناصر الإعدادات - متقدم (⭐⭐⭐): استخدم
mktempلإنشاء دليل مؤقت يُنظَّف تلقائيًا عند الانتهاء، واكتب سكريبت نشر بمستوى إنتاج — مع تحليل معاملات، وتسجيل ملون، ومعالجة أخطاء، وفحص صحة