Linux: أساسيات سكريبت Bash — المتغيرات / الشروط / الحلقات
احفظ سلسلة من أوامر لينكس في ملف، وأضف بعض التحكم المنطقي (شروط، حلقات)، فتحصل على سكريبت صدفة (Shell script). هذه هي نقطة البداية لأتمتة كل العمل المتكرر.
📋 المتطلبات المسبقة: يجب أن تعرف بالفعل
- الدرس 19: الأرشفة والضغط
1. ما ستتعلمه
- Shebang وصلاحيات التنفيذ
- تعريف المتغيرات واختلافات علامات الاقتباس
- المعاملات الموضعية
- المنطق الشرطي if/test
- حلقات for/while
2. قصة من النشر اليدوي إلى النشر بنقرة واحدة
(1) نقطة الألم: كتابة نفس الأوامر الخمسة كل يوم
كان شياو مينغ يُنفّذ هذه الأوامر كل يوم للنشر: git pull، وnpm install، وnpm run build، وpm2 restart app، وecho "done". 5 أوامر، 3 دقائق، متكررة يوميًا.
(2) سكريبت من 8 أسطر
كتب deploy.sh:
#!/bin/bash
set -e
echo "Starting deployment..."
cd /var/www/myapp
git pull origin main
npm install
npm run build
pm2 restart app
echo "✅ Deployment complete!"
set -e هو خط الدفاع الأول لسلامة السكريبت — السكريبت يتوقف فورًا إذا أعاد أي أمر رمز خروج غير صفري. بدون set -e، يستمر السكريبت حتى بعد فشل أمر، مما قد يسبب أخطاء متسلسلة خطيرة (مثل إعادة تشغيل خدمة بكود معطوب).
(3) الفائدة: نشر بنقرة واحدة
منذ ذلك الحين، صار شياو مينغ يحتاج فقط تشغيل ./deploy.sh — كل الخطوات تُنجز تلقائيًا، موفرًا الوقت ومتجنبًا النسيان.
3. نقاط المعرفة
(1) Shebang
السطر الأول من السكريبت يجب أن يكون Shebang — يخبر النظام أي مفسر يستخدم:
#!/bin/bash # التنفيذ بـbash
#!/bin/sh # التنفيذ بـsh (وضع التوافق)
#!/usr/bin/env python3 # التنفيذ بـPython 3
#!/bin/bash هو Shebang — يخبر نظام التشغيل أي مفسر يستخدم للسكريبت. بدون هذا السطر، يستخدم النظام الصدفة الحالية، مما قد يسبب مشاكل توافق (مثل أن sh لا يدعم صياغة [[ ]]). كل سكريبتات bash يجب أن تبدأ بـ#!/bin/bash.
(2) المتغيرات
# تعريف متغيرات (بدون مسافات حول علامة التساوي)
name="Alice"
age=25
# استخدام المتغيرات (الأقواس المعقوفة مُوصى بها)
echo $name # Alice
echo ${name} # Alice (الأقواس أأمن)
echo "Hello, ${name}"
# قواعد تسمية المتغيرات
# 1. أحرف، أرقام، شرطات سفلية
# 2. لا يمكن أن تبدأ برقم
# 3. حساسة لحالة الأحرف (NAME != name)
ثلاثة أنواع من علامات الاقتباس:
| علامة الاقتباس | مثال | السلوك |
|---|---|---|
| علامات مزدوجة | "Hello $name" |
تحليل المتغيرات والأوامر |
| علامات مفردة | 'Hello $name' |
نص حرفي، بدون تحليل |
علامات خلفية/$() |
`date` أو $(date) |
تنفيذ أمر، التقاط المخرجات |
ℹ️ ملاحظة: الفرق الجوهري بين علامات الاقتباس المفردة والمزدوجة — في المزدوجة، يُحلّل
$variableو$(command)ويُستبدل؛ كل شيء في المفردة حرفي. استخدم المزدوجة عند دمج المتغيرات، والمفردة عندما تحتاج حفظ$وأحرف خاصة أخرى (مثل أنماط regex أو heredocs حيث لا تريد توسيع المتغيرات).
name="Alice"
echo "Hello $name" # Hello Alice
echo 'Hello $name' # Hello $name
echo "Today is $(date)" # Today is Tue Jul 7 10:23:45 CST 2026
(3) المعاملات الموضعية
./script.sh arg1 arg2 arg3
# $1 $2 $3
$0 # اسم السكريبت نفسه
$1, $2 # المعامل الأول، المعامل الثاني
$# # عدد المعاملات
$@ # كل المعاملات (ككلمات منفصلة)
$* # كل المعاملات (كنص واحد)
$? # رمز خروج الأمر السابق (0=نجاح، غير صفري=فشل)
$$ # PID السكريبت الحالي
(4) المنطق الشرطي (if / test / [[ ]])
# أمر test أو [ ]
if [ "$name" = "Alice" ]; then
echo "Hello Alice"
fi
# [[ ]] الأقوى (خاص بـbash)
if [[ "$name" == "Alice" ]]; then
echo "Hello Alice"
fi
# if-elif-else
if [ "$1" -gt 10 ]; then
echo "Greater than 10"
elif [ "$1" -gt 5 ]; then
echo "Greater than 5"
else
echo "5 or less"
fi
اختبارات الملفات:
| الشرط | المعنى |
|---|---|
-f file |
ملف عادي |
-d dir |
دليل |
-e path |
موجود |
-s file |
حجم الملف > 0 |
-r file |
قابل للقراءة |
-w file |
قابل للكتابة |
-x file |
قابل للتنفيذ |
مقارنة النصوص:
| الشرط | المعنى |
|---|---|
$a = $b |
متساوي |
$a != $b |
غير متساوي |
-z $a |
النص فارغ (الطول = 0) |
-n $a |
النص غير فارغ |
المقارنة العددية:
| الشرط | المعنى |
|---|---|
$a -eq $b |
يساوي |
$a -ne $b |
لا يساوي |
$a -gt $b |
أكبر من |
$a -ge $b |
أكبر من أو يساوي |
$a -lt $b |
أصغر من |
$a -le $b |
أصغر من أو يساوي |
(5) الحلقات
# حلقة for
for i in 1 2 3 4 5; do
echo "Number: $i"
done
for file in *.txt; do
echo "Found file: $file"
done
for i in {1..10}; do
echo "$i"
done
# حلقة while
count=1
while [ $count -le 5 ]; do
echo "Attempt $count"
count=$((count + 1))
done
# قراءة ملف سطرًا بسطر
while IFS= read -r line; do
echo "$line"
done < /etc/hostname
(6) ▶ مثال: سكريبت أساسي
#!/bin/bash
# hello.sh - أول سكريبت bash
echo "Hello, World!"
echo "Current user: $(whoami)"
echo "Current directory: $(pwd)"
echo "File count: $(ls | wc -l)"
الإخراج:
TEXTHello, World! Current user: alice Current directory: /home/alice File count: 12
(7) ▶ مثال: استخدام المعاملات الموضعية
#!/bin/bash
# greet.sh - تحية مستخدم محدد
if [ $# -eq 0 ]; then
echo "Usage: $0 <username>"
exit 1
fi
echo "Hello, $1!"
echo "Welcome to Linux scripting."
echo "Script name: $0"
echo "Argument count: $#"
./greet.sh Alice
# Hello, Alice!
(8) ▶ مثال: شرط if
#!/bin/bash
# check-file.sh - فحص حالة ملف
FILE="${1:-test.txt}"
if [ ! -e "$FILE" ]; then
echo "File '$FILE' does not exist"
exit 1
fi
if [ -f "$FILE" ]; then
echo "Type: Regular file"
elif [ -d "$FILE" ]; then
echo "Type: Directory"
fi
[ -r "$FILE" ] && echo "Readable: ✅" || echo "Readable: ❌"
[ -w "$FILE" ] && echo "Writable: ✅" || echo "Writable: ❌"
[ -x "$FILE" ] && echo "Executable: ✅" || echo "Executable: ❌"
(9) ▶ مثال: عملية دفعية بحلقة for
#!/bin/bash
# rename.sh - إعادة تسمية دفعية من .txt إلى .md
count=0
for file in *.txt; do
if [ -f "$file" ]; then
mv "$file" "${file%.txt}.md"
echo "Renamed: $file → ${file%.txt}.md"
count=$((count + 1))
fi
done
echo "Total files renamed: $count"
(10) ▶ مثال: حلقة while وقراءة المدخلات
#!/bin/bash
# countdown.sh - عدّ تنازلي
if [ $# -ne 1 ]; then
echo "Usage: $0 <seconds>"
exit 1
fi
count=$1
while [ $count -gt 0 ]; do
echo "Countdown: $count"
count=$((count - 1))
sleep 1
done
echo "Time's up!"
(11) ▶ مثال شامل: سكريبت معالجة دفعية للملفات
#!/bin/bash
# batch-process.sh - معالجة دفعية لملفات في دليل
DIR="${1:-.}"
ACTION="${2:-info}"
if [ ! -d "$DIR" ]; then
echo "Error: Directory '$DIR' does not exist"
exit 1
fi
echo "Processing directory: $DIR"
echo "Action: $ACTION"
echo "---"
case "$ACTION" in
info)
echo "File statistics:"
echo " Total files: $(find "$DIR" -type f | wc -l)"
echo " Total directories: $(find "$DIR" -type d | wc -l)"
echo " Total size: $(du -sh "$DIR" | cut -f1)"
;;
backup)
BACKUP="/tmp/backup-$(date +%Y%m%d)"
mkdir -p "$BACKUP"
cp -r "$DIR" "$BACKUP/"
echo "Backup complete: $BACKUP"
;;
clean)
echo "Cleaning .log files..."
find "$DIR" -name "*.log" -delete
echo "Cleaning .tmp files..."
find "$DIR" -name "*.tmp" -delete
echo "Cleaning complete"
;;
*)
echo "Actions: info / backup / clean"
;;
esac
❓ أسئلة شائعة
س: هل
#!/bin/bashفي السطر الأول إجباري؟ ج: نعم. يخبر النواة أي مفسر تستخدم. بدونه، الافتراضي هو الصدفة الحالية (مما قد يسبب مشاكل توافق).
س: لماذا يقول سكريبتي
command not found؟ ج: سببان: 1) لا توجد صلاحية تنفيذ — شغّلchmod +x script.sh. 2) الدليل الحالي ليس في PATH — استخدم./script.shبدلًا منscript.sh.
س: ما الفرق بين
$@و$*؟ ج:$@يعامل كل معامل كـ"كلمة" منفصلة؛ و$*يجمع كل المعاملات في نص واحد. معظم السيناريوهات توصي بـ$@. التكرار بـfor arg in "$@"; doهو الممارسة القياسية.
س: ما الفرق بين
exit 0وexit 1؟ ج:exit 0يعني نجاح؛ وexit non-zeroيعني فشل (أرقام مختلفة تدل على أنواع أخطاء مختلفة). البرامج الأخرى يمكنها فحص$?لمعرفة هل نجح السكريبت.
س: كيف أحصل على مدخلات المستخدم في سكريبت؟ ج: استخدم أمر
read:read -p "Enter your name: " name، ثم$nameيحتوي على مدخلات المستخدم.
📖 ملخص
- Shebang
#!/bin/bash، وchmod +xيمنح صلاحية التنفيذ - المتغيرات:
name="value"، واستخدمها بـ$nameأو${name} - علامات مزدوجة تحلّل المتغيرات، ومفردة حرفية، و
$(cmd)ينفّذ أمرًا if [ condition ]للمنطق الشرطي، وfor i in listللحلقاتexit 0للنجاح، وexit 1للخطأ
📝 تمارين
- أساسي (⭐): اكتب سكريبت
info.shيُخرج اسم المستخدم الحالي، والتاريخ، والدليل الحالي، وعدد الملفات - متوسط (⭐⭐): اكتب سكريبت
check-file.shيأخذ اسم ملف كمعامل، ويتحقق من وجوده ويُخرج نوعه. ثم اكتب سكريبتbackup-txt.shينسخ كل ملفات.txtاحتياطيًا من الدليل الحالي إلى/tmp/txt-backup/ - متقدم (⭐⭐⭐): اكتب سكريبت عدّ تنازلي يأخذ عدد ثوانٍ كمعامل ويعدّ تنازليًا كل ثانية، وأضف معالجة أخطاء
set -e. ثم استخدم حلقةforلإنشاء 10 أدلة باسمtest-1حتىtest-10، وأخيرًا اكتب سكريبتًا يحسب ويرتّب أحجام تلك الأدلة العشرة