0Pricing
DevOps Bootcamp · درس

بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها

نظّم المساعدات المشتركة في ملفات مكتبة .sh القابلة للمصدر باستخدام حواجز التضمين وبوادئ دوال ذات مساحات أسماء.

بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها درس مجاني في DevOps Bootcamp على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DevOps Bootcamp، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DevOps Bootcamp 4 دروس في المجموع.

ما مكتبة Bash؟

في هندسة البرمجيات، المكتبة هي مجموعة من الدوال القابلة لإعادة الاستخدام التي يمكن لبرامج متعددة مشاركتها. وتدعم Bash المفهوم نفسه من خلال سكربتات الصدفة القابلة للاستيراد.

بدلًا من نسخ دوال المساعدة ولصقها في كل سكربت، ضعها في ملف .sh مخصص وحمّله باستخدام الأمر source (أو اختصاره .). تصبح أي دوال أو متغيرات أو أسماء مستعارة معرّفة في ذلك الملف متاحة في جلسة الصدفة الحالية للسكربت المستدعي.

  • يعزز مبدأ DRY (لا تكرر نفسك)
  • يمركز إصلاحات الأخطاء — أصلحها مرة واحدة ليستفيد جميع المستدعين
  • يجعل السكربتات الفردية أقصر وأسهل قراءة
  • يتيح الاتساق على مستوى الفريق في التسجيل ومعالجة الأخطاء والمنطق البرمجي للأدوات

يحتوي مشروع Bash المنظم جيدًا عادةً على مجلد lib/ يضم هذه الملفات المشتركة، بما يحاكي اصطلاحات اللغات الأعلى مستوى.

أمر source وعامل النقطة

هناك طريقتان متكافئتان لتحميل ملف مكتبة إلى بيئة الصدفة الحالية:

  • source /path/to/lib.sh — الصيغة الصريحة سهلة القراءة
  • . /path/to/lib.sh — الاختصار المتوافق مع POSIX

ينفّذ كلاهما الملف في عملية الصدفة الحالية وليس في صدفة فرعية، لذلك تصبح كل دالة ومتغير معرّف فيه جزءًا من بيئة السكربت مباشرةً بعد الاستدعاء.

من الأنماط الشائعة تحديد موقع المكتبة نسبةً إلى السكربت المستدعي باستخدام $BASH_SOURCE، ما يجعل المشروع محمولًا بغض النظر عن مكان تثبيته.

#!/usr/bin/env bash
# main.sh — load a library relative to this script's own location

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/utils.sh"

echo "Library loaded. Calling greet..."
greet "World"

إنشاء أول ملف مكتبة لك

ملف المكتبة هو ملف .sh عادي لا يحتوي إلا على تعريفات الدوال (وأحيانًا الثوابت). وينبغي ألا ينفّذ أي شيفرة ذات آثار جانبية على المستوى الأعلى — إذ يُراد استيراده، لا تنفيذه مباشرةً.

الاصطلاحات الأساسية:

  • ابدأ بتعليق shebang يصف الغرض من المكتبة
  • عرّف الدوال فقط — لا تضع منطق main على المستوى الأعلى
  • استخدم return داخل الدوال (ولا تستخدم exit مطلقًا، إذ سيُنهي المستدعي)
  • احتفظ بالملف في مجلد فرعي lib/ ضمن مشروعك
#!/usr/bin/env bash
# lib/utils.sh — General-purpose utility functions

# Print a greeting message
greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

# Print a timestamped log line to stderr
log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

# Print an error message and return a failure code
log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

حواجز التضمين: منع الاستيراد المزدوج

عندما تستورد عدة سكربتات المكتبة نفسها، أو عندما تستورد مكتبةٌ مكتبةً أخرى تستوردها أيضًا السكربتة الرئيسية، يمكن تعريف الدوال عدة مرات. يهدر ذلك الوقت وقد يسبب أخطاء دقيقة إذا استُبدل جسم دالة أثناء التشغيل.

الحل هو حارس التضمين — متغير يعمل كعلامة. عند التحميل الأول يكون المتغير غير مضبوط، فيتابع الملف التنفيذ. وفي كل تحميل لاحق يكون الحارس مضبوطًا مسبقًا، فيعود الملف فورًا.

هذا هو النظير في Bash للتوجيه #pragma once في C/C++ أو لأنماط if not already imported في اللغات الأخرى.

#!/usr/bin/env bash
# lib/utils.sh — with include guard

# Guard: if already sourced, do nothing
[[ -n "${_LIB_UTILS_LOADED:-}" ]] && return 0
_LIB_UTILS_LOADED=1

greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

بادئات الدوال ذات مساحات الأسماء

لدى Bash مساحة أسماء عامة واحدة للدوال. إذا عرّفت مكتبتان دالة باسم log أو init، فسيستبدل التعريف الثاني الأول بصمت.

والوسيلة القياسية للدفاع عن ذلك هي بادئة مساحة الأسماء: إذ تُسبق كل دالة في المكتبة بالاسم المختصر للمكتبة متبوعًا بعلامتي النقطتين (::) أو بشرطة سفلية. فمثلًا، تستخدم مكتبة أدوات السلاسل النصية str::، بينما تستخدم مكتبة الملفات file::.

  • تصبح التعارضات غير محتملة للغاية
  • تصبح الشيفرة المستدعية موضحة لذاتها — إذ تخبرك str::trim تحديدًا بمكان وجود الدالة
  • تتحسن قابلية البحث باستخدام Grep: يعرض grep 'str::' main.sh فورًا جميع استدعاءات مكتبة السلاسل النصية
#!/usr/bin/env bash
# lib/str.sh — String utility library (namespaced)

[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1

# Trim leading and trailing whitespace
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

# Convert string to uppercase
str::upper() {
    echo "${1^^}"
}

# Convert string to lowercase
str::lower() {
    echo "${1,,}"
}

# Check if a string contains a substring
str::contains() {
    [[ "$1" == *"$2"* ]]
}

تنظيم مجلد lib/

مع نمو المشروع، يصبح ملف utils.sh واحدًا صعب الإدارة. فقسّم المسؤوليات على ملفات مكتبة متخصصة داخل مجلد lib/:

  • lib/log.sh — أدوات التسجيل (log::info وlog::warn وlog::error)
  • lib/str.sh — معالجة السلاسل النصية (str::trim وstr::upper)
  • lib/fs.sh — أدوات نظام الملفات (fs::require_dir وfs::safe_rm)
  • lib/net.sh — فحوصات الشبكة (net::wait_for_port وnet::is_online)

يمكن لملف واحد هو محمّل الإقلاع (lib/bootstrap.sh) استيرادها كلها بالترتيب الصحيح، بحيث لا يحتاج كل سكربت إلا إلى استدعاء source واحد.

#!/usr/bin/env bash
# lib/bootstrap.sh — Load all project libraries in dependency order

[[ -n "${_LIB_BOOTSTRAP_LOADED:-}" ]] && return 0
_LIB_BOOTSTRAP_LOADED=1

_BOOTSTRAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "${_BOOTSTRAP_DIR}/log.sh"
source "${_BOOTSTRAP_DIR}/str.sh"
source "${_BOOTSTRAP_DIR}/fs.sh"
source "${_BOOTSTRAP_DIR}/net.sh"

log::info "All libraries loaded."

مكتبة متكاملة لتسجيل السجلات

يُعد تسجيل السجلات من أكثر الاهتمامات المشتركة شيوعًا بين البرامج النصية. تعمل مكتبة مخصصة مثل lib/log.sh على توحيد تنسيق المخرجات، ومستويات السجل، وأكواد الألوان.

وبوجود مكتبة كهذه، يطبع كل برنامج نصي في المشروع رسائل متسقة مرفقة بالطوابع الزمنية ومشفرة بالألوان، من دون تكرار أي كود خاص بالتنسيق.

#!/usr/bin/env bash
# lib/log.sh — Coloured, levelled logging library

[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1

# Colour codes (disabled when not writing to a terminal)
_LOG_RED=''; _LOG_YEL=''; _LOG_GRN=''; _LOG_RST=''
if [[ -t 2 ]]; then
    _LOG_RED='\033[0;31m'
    _LOG_YEL='\033[0;33m'
    _LOG_GRN='\033[0;32m'
    _LOG_RST='\033[0m'
fi

_log::_print() {
    local level="$1" colour="$2"; shift 2
    printf "%b[%s]%b %s  %s\n" \
        "$colour" "$level" "$_LOG_RST" \
        "$(date '+%H:%M:%S')" "$*" >&2
}

log::info()  { _log::_print 'INFO ' "$_LOG_GRN" "$@"; }
log::warn()  { _log::_print 'WARN ' "$_LOG_YEL" "$@"; }
log::error() { _log::_print 'ERROR' "$_LOG_RED" "$@"; return 1; }
log::fatal() { _log::_print 'FATAL' "$_LOG_RED" "$@"; exit 1; }

مكتبة مساعدات لنظام الملفات

غالبًا ما تكرر البرامج النصية التي تتعامل مع الملفات والأدلة الفحوصات الوقائية نفسها: هل هذا الدليل موجود؟ هل يمكن الكتابة في هذا المسار؟ هل أوشكت على حذف شيء مهم؟

يؤدي توحيد هذه الفحوصات في lib/fs.sh إلى جعل كل برنامج نصي مستهلك أكثر أمانًا وأسهل قراءة. لاحظ أن كل دالة تستخدم return 1 عند الفشل بدلًا من exit، مما يحافظ على قدرة المستدعي على معالجة الخطأ بطريقة سلسة.

#!/usr/bin/env bash
# lib/fs.sh — Filesystem helper library

[[ -n "${_LIB_FS_LOADED:-}" ]] && return 0
_LIB_FS_LOADED=1

# Ensure a directory exists; create it if not
fs::require_dir() {
    local dir="$1"
    if [[ ! -d "$dir" ]]; then
        mkdir -p "$dir" || { echo "[fs] Cannot create directory: $dir" >&2; return 1; }
    fi
}

# Remove a file only if it exists (no error on missing)
fs::safe_rm() {
    local target="$1"
    [[ -e "$target" ]] && rm -rf -- "$target"
    return 0
}

# Assert that a file exists and is readable
fs::require_file() {
    local file="$1"
    [[ -f "$file" && -r "$file" ]] || {
        echo "[fs] Required file missing or unreadable: $file" >&2
        return 1
    }
}

إصدار مكتبتك باستخدام ثابت

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

يمكن للمستدعين بعد ذلك التحقق من وجود إصدار أدنى عند بدء التشغيل، واكتشاف حالات عدم التوافق مبكرًا بدلًا من قضاء الوقت لاحقًا في تصحيح أخطاء الإخفاقات الغامضة. ويؤدي متغير الحماية أيضًا دور سلسلة الإصدار، فيجمع مسؤوليتين في متغير واحد.

#!/usr/bin/env bash
# lib/str.sh — versioned example

# Guard doubles as the version identifier
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
readonly _LIB_STR_LOADED='1.3.0'

# Caller can validate the version
str::version() { echo "$_LIB_STR_LOADED"; }

# ---- Utility functions ----
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

str::repeat() {
    local str="$1" count="$2" result=''
    for (( i=0; i<count; i++ )); do result+="$str"; done
    echo "$result"
}

عرض توضيحي مستقل: استخدام مكتبات متعددة

يوضح هذا المشهد برنامجًا نصيًا واقعيًا يستورد مكتبتين باستخدام source ويستخدم دوال من كل منهما. لاحظ أن البرنامج النصي الرئيسي يظل مرتبًا — فهو يعبّر عن الهدف، بينما توجد كل تفاصيل التنفيذ داخل المكتبات.

يمكن تشغيل البرنامج النصي كملف مستقل لأنه يعرّف المكتبتين داخليًا باستخدام مستندات here-documents مكتوبة في ملفات مؤقتة. أما في مشروع حقيقي، فستوجد كل مكتبة في ملفها الخاص ضمن lib/.

#!/usr/bin/env bash
# Standalone demo: inline libs written to /tmp, then sourced
set -euo pipefail

# --- Create a temporary lib/log.sh ---
TMPDIR_LIBS="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_LIBS"' EXIT

cat > "${TMPDIR_LIBS}/log.sh" <<'LIBEOF'
[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1
log::info()  { echo "[INFO]  $*"; }
log::error() { echo "[ERROR] $*" >&2; return 1; }
LIBEOF

cat > "${TMPDIR_LIBS}/str.sh" <<'LIBEOF'
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
str::upper() { echo "${1^^}"; }
str::trim()  { local s="$1"; s="${s#"${s%%[![:space:]]*}"}";
               s="${s%"${s##*[![:space:]]}"}"  ; echo "$s"; }
LIBEOF

# --- Source both libraries ---
source "${TMPDIR_LIBS}/log.sh"
source "${TMPDIR_LIBS}/str.sh"

# --- Main logic ---
log::info "Libraries loaded successfully."
raw_input="   hello from bash libraries   "
trimmed="$(str::trim "$raw_input")"
log::info "Trimmed: '${trimmed}'"
log::info "Uppercased: '$(str::upper "$trimmed")'"

أفضل الممارسات والأخطاء الشائعة

قبل تسليم مكتبة لاستخدام الفريق، راجع قائمة التحقق التالية:

  • حارس التضمين — يجب أن تحتوي عليه كل مكتبة؛ وثّق اسم متغير الحماية في الأعلى
  • لا توجد آثار جانبية على المستوى الأعلى — لا تستخدم cd أو echo ولا تعدّل الحالة العامة خارج جسم دالة
  • استخدم local لجميع المتغيرات داخل الدوال — من دون local، يتسرب كل إسناد إلى نطاق المستدعي
  • أعد قيمة، ولا تخرج أبدًا — يؤدي استخدام exit داخل ملف مستورد إلى إنهاء الصدفة المستدعية بالكامل
  • تحقق من المدخلات — افحص الوسائط المطلوبة وأعد رمز خطأ ذا معنى عند غيابها
  • وثّق باستخدام التعليقات — صف ما تفعله كل دالة، ومعلماتها، وقيمة الإرجاع الخاصة بها
  • تجنب set -e داخل ملفات المكتبات — قد تكون لدى المستدعين استراتيجية خاصة لمعالجة الأخطاء؛ اترك لهم قرار استخدامها

اختبار المعرفة: حراس التضمين

لنفترض وجود مشروع يستورد فيه main.sh كلًا من lib/bootstrap.sh وlib/log.sh، بينما يستورد lib/bootstrap.sh أيضًا lib/log.sh داخليًا. ما الغرض الأساسي من حارس التضمين في lib/log.sh؟

مراجعة الدرس: إتقان مكتبات Bash

لقد غطيتم جميع الأساليب الأساسية لبناء مكتبات Bash القابلة لإعادة الاستخدام واستهلاكها. إليكم أهم ما ينبغي تذكره:

  • استوردوا باستخدام source أو . — يحمّل ملفًا في الصدفة الحالية، مما يجعل دواله متاحة فورًا
  • استخدموا $BASH_SOURCE لحل مسارات المكتبات نسبةً إلى البرنامج النصي المستدعي، مما يحافظ على قابلية نقل المشاريع
  • حراس التضمين ([[ -n "${_GUARD:-}" ]] && return 0) تمنع التعريفات المكررة عند استيراد المكتبة نفسها من ملفات متعددة
  • بادئات مساحات الأسماء (log::، str::، fs::) تزيل تعارضات أسماء الدوال بين المكتبات
  • دليل lib/ الذي يحتوي على ملفات مركزة ذات مسؤولية واحدة يحافظ على قابلية صيانة المشاريع الكبيرة
  • محمّل تمهيدي (lib/bootstrap.sh) يمنح كل برنامج نصي استدعاءً واحدًا لـ source لتحميل المنظومة كاملة
  • لا تستخدموا exit أو الآثار الجانبية على المستوى الأعلى أبدًا في ملفات المكتبات — لا مكان هناك إلا لتعريفات الدوال والثوابت

طبّقوا هذه الأنماط باستمرار، وستصبح برامج shell النصية لديكم معيارية وقابلة للصيانة بقدر الكود المكتوب بأي لغة عالية المستوى.

الأسئلة الشائعة

هل درس «بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها» مجاني؟

نعم — نص درس «بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DevOps Bootcamp، انتقل إلى CoddyKit PRO. تتضمن دورة DevOps Bootcamp 4 دروس في المجموع.

ماذا ستتعلم في «بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها»؟

نظّم المساعدات المشتركة في ملفات مكتبة .sh القابلة للمصدر باستخدام حواجز التضمين وبوادئ دوال ذات مساحات أسماء. تتمرن على DevOps Bootcamp مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ DevOps Bootcamp؟

لا تُشترط خبرة سابقة. DevOps Bootcamp على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس DevOps Bootcamp هذا؟

نعم. كل درس في DevOps Bootcamp يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع
  2. بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها
  3. تحليل الأعلام والوسائط باستخدام getopts
  4. تمرير المصفوفات والخرائط الترابطية بين الدوال
← العودة إلى DevOps Bootcamp