تخطَّ إلى المحتوى

🐧 شرح Linux وسطر الأوامر

Bash Scripting — مستوى متقدم

الدرس 26 من 27· ⏱ 6 دقائق قراءة

Bash Scripting — مستوى متقدم

الدوال (Functions) مع المتغيرات المحلية

#!/bin/bash

# تعريف دالة مع متغيرات محلية
process_file() {
  local file="$1"
  local max_lines="${2:-100}"
  local count=0

  while IFS= read -r line; do
    ((count++))
    [[ $count -gt $max_lines ]] && break
    echo "$line"
  done < "$file"
}

# دالة تُرجع قيمة
get_file_size() {
  local file="$1"
  local size
  size=$(stat -c%s "$file" 2>/dev/null) || return 1
  echo "$size"
  return 0
}

# استخدام
result=$(get_file_size "data.txt") && echo "Size: $result bytes"

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

مصفوفات عادية

# تعريف
fruits=("تفاح" "موز" "برتقال" "عنب")

# إضافة عناصر
fruits+=("فراولة")

# الوصول
echo "${fruits[0]}"    # تفاح
echo "${fruits[@]}"    # جميع العناصر
echo "${#fruits[@]}"   # عدد العناصر

# التكرار
for fruit in "${fruits[@]}"; do
  echo "$fruit"
done

# التقطيع
echo "${fruits[@]:1:2}"  # موز برتقال

مصفوفات Associative (القواميس)

# تعريف (تتطلب Bash 4+)
declare -A config

config=(
  ["db_host"]="localhost"
  ["db_port"]="27017"
  ["db_user"]="admin"
)

# الوصول
echo "${config[db_host]}"

# التكرار على المفاتيح والقيم
for key in "${!config[@]}"; do
  echo "$key = ${config[$key]}"
done

# التحقق من وجود مفتاح
if [[ -v config["db_host"] ]]; then
  echo "db_host موجود"
fi

معالجة النصوص (String Manipulation)

text="  Hello, World! Bash Scripting  "

# إزالة المسافات
trimmed="${text#"${text%%[![:space:]]*}"}"
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"

# استبدال
echo "${text/World/المستخدم}"        # أول تطابق
echo "${text// /-}"                  # كل المسافات
echo "${text/#  /}"                  # بداية السطر
echo "${text/%  /}"                  # نهاية السطر

# تحويل حالة الأحرف (Bash 4+)
upper="${text^^}"                     # إلى uppercase
lower="${text,,}"                     # إلى lowercase

# استخراج جزء
echo "${text:7:5}"                    # World
echo "${text: -9}"                    # Scripting

# التقسيم باستخدام IFS
IFS=',' read -ra parts <<< "a,b,c,d"
for part in "${parts[@]}"; do
  echo "$part"
done

# الطول
echo "${#text}"

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

set -e — التوقف عند أول خطأ

#!/bin/bash
set -e  # يخرج عند أول أمر يفشل
set -u  # يخرج عند استخدام متغير غير معرّف
set -o pipefail  # يفشل pipeline إذا فشل أي أمر فيه

# أو كلها معًا
set -euo pipefail

trap — التقاط الإشارات

#!/bin/bash
set -euo pipefail

cleanup() {
  local exit_code=$?
  echo "تنظيف..."
  rm -f /tmp/temp_$$.txt
  [[ $exit_code -ne 0 ]] && echo "انتهى بخطأ: $exit_code"
  exit $exit_code
}

error_handler() {
  local line=$1
  local command=$2
  echo "خطأ في السطر $line: $command"
  exit 1
}

trap cleanup EXIT
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR

# استخدام
mkdir -p /tmp/test_$$
echo "بيانات مهمة" > /tmp/temp_$$.txt

إنشاء دوال معالجة أخطاء

die() {
  echo "خطأ: $*" >&2
  exit 1
}

warn() {
  echo "تحذير: $*" >&2
}

require_command() {
  command -v "$1" >/dev/null 2>&1 \
    || die "الأمر '$1' غير موجود. قم بتثبيته أولاً."
}

require_command "mongosh"
require_command "docker"

validate_file() {
  [[ -f "$1" ]] || die "الملف '$1' غير موجود"
  [[ -r "$1" ]] || die "لا يمكن قراءة الملف '$1'"
  [[ -s "$1" ]] || die "الملف '$1' فارغ"
}

التصحيح (Debugging)

set -x — عرض الأوامر قبل تنفيذها

#!/bin/bash

debug_mode=${DEBUG:-false}

if [[ "$debug_mode" == "true" ]]; then
  set -x
fi

# جزء محدد فقط
echo "بدء التشغيل..."
{ set -x; } 2>/dev/null
# أوامر للتصحيح
ls -la /var/log
df -h
{ set +x; } 2>/dev/null
echo "انتهى..."

وسائط سطر الأوامر (getopts)

#!/bin/bash

usage() {
  echo "الاستخدام: $0 [-f file] [-o output] [-v] [-h]"
  echo "  -f    ملف الإدخال (إجباري)"
  echo "  -o    ملف الإخراج (اختياري)"
  echo "  -v    وضع المفصل (verbose)"
  echo "  -h    عرض المساعدة"
  exit 1
}

verbose=false
input_file=""
output_file=""

while getopts "f:o:vh" opt; do
  case $opt in
    f) input_file="$OPTARG" ;;
    o) output_file="$OPTARG" ;;
    v) verbose=true ;;
    h) usage ;;
    \?) echo "خيار غير معروف: -$OPTARG" >&2; usage ;;
  esac
done

[[ -z "$input_file" ]] && die "الملف الإجباري مفقود: -f"

$verbose && echo "وضع المفصل مفعّل"
echo "معالجة الملف: $input_file"

Regular Expressions في Bash

#!/bin/bash

validate_input() {
  local input="$1"

  # التحقق من البريد الإلكتروني
  if [[ "$input" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "بريد إلكتروني صالح"
    return 0
  fi

  # التحقق من رقم الهاتف (سعودي)
  if [[ "$input" =~ ^(05|5)[0-9]{8}$ ]]; then
    echo "رقم هاتف صالح"
    return 0
  fi

  # التحقق من IP
  if [[ "$input" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
    local IFS='.'
    read -ra octets <<< "$input"
    for octet in "${octets[@]}"; do
      ((octet < 0 || octet > 255)) && return 1
    done
    echo "IP صالح"
    return 0
  fi

  echo "مدخل غير صالح"
  return 1
}

# استخدام BASH_REMATCH
parse_log_line() {
  local line="$1"
  local pattern='^([0-9]{4}-[0-9]{2}-[0-9]{2}) ([0-9]{2}:[0-9]{2}:[0-9]{2}) \[([A-Z]+)\] (.+)$'

  if [[ "$line" =~ $pattern ]]; then
    echo "التاريخ: ${BASH_REMATCH[1]}"
    echo "الوقت: ${BASH_REMATCH[2]}"
    echo "المستوى: ${BASH_REMATCH[3]}"
    echo "الرسالة: ${BASH_REMATCH[4]}"
  fi
}

مثال متكامل

#!/bin/bash
set -euo pipefail

SCRIPT_NAME=$(basename "$0")
VERSION="1.0.0"

log() {
  local level="$1"
  shift
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
}

die() { log "ERROR" "$*"; exit 1; }
info() { log "INFO" "$*"; }
warn() { log "WARN" "$*"; }

cleanup() {
  info "تنظيف الملفات المؤقتة..."
  rm -rf "$TEMP_DIR"
}

trap cleanup EXIT

usage() {
  cat <<EOF
$SCRIPT_NAME v$VERSION
الاستخدام: $SCRIPT_NAME -d <directory> [-p <pattern>] [-v]

خيارات:
  -d    المسار للبحث (إجباري)
  -p    نمط الملفات (default: *.log)
  -v    عرض تفاصيل إضافية
  -h    عرض المساعدة
EOF
  exit 0
}

search_dir=""
file_pattern="*.log"
verbose=false

while getopts "d:p:vh" opt; do
  case $opt in
    d) search_dir="$OPTARG" ;;
    p) file_pattern="$OPTARG" ;;
    v) verbose=true ;;
    h) usage ;;
    *) usage ;;
  esac
done

[[ -z "$search_dir" ]] && die "المسار إجباري: -d"
[[ -d "$search_dir" ]] || die "المسار غير موجود: $search_dir"

TEMP_DIR=$(mktemp -d)
$verbose && info "المجلد المؤقت: $TEMP_DIR"

declare -A file_stats
total_lines=0
file_count=0

while IFS= read -r -d '' file; do
  ((file_count++))
  lines=$(wc -l < "$file")
  file_stats["$file"]=$lines
  total_lines=$((total_lines + lines))
  $verbose && info "$file: $lines سطر"
done < <(find "$search_dir" -name "$file_pattern" -type f -print0)

echo "=== الملخص ==="
echo "الملفات: $file_count"
echo "إجمالي الأسطر: $total_lines"

# ترتيب وعرض أكبر 5 ملفات
for file in "${!file_stats[@]}"; do
  echo "${file_stats[$file]}:$file"
done | sort -rn | head -5 | while IFS=':' read -r lines file; do
  echo "  $lines سطر - $file"
done

تمارين

  1. اكتب دالة تأخذ مسار ملف وتتحقق من وجوده، صلاحيات القراءة، وحجمه
  2. أنشئ script مع getopts لقبول -i (input) و -o (output) مع التحقق من وجود input
  3. استخدم مصفوفة associative لتخزين إعدادات اتصال قاعدة بيانات وطباعتها
  4. أضف معالجة أخطاء متكاملة مع trap لتنظيف الملفات المؤقتة عند أي خطأ
شرح Bash Scripting — مستوى متقدم — Linux وسطر الأوامر بالعربي
Bash Scripting — مستوى متقدمLinux وسطر الأوامر بالعربي · The Code Fix

📚 لمزيد من التعمّق في Linux وسطر الأوامر، راجِع صفحات دليل Linux الرسمية.

هل كان هذا الدرس مفيدًا؟