تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع
اكتب دوالًا تستخدم متغيرات محلية وحالات خروج وقيم إرجاع مبنية على printf بدلًا من المتغيرات العامة الهشة.
تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع درس مجاني في Linux Command Line & Bash Scripting Mastery على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Linux Command Line & Bash Scripting Mastery، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.
أهمية نطاق الدالة
تكون المتغيرات في Bash عالمية افتراضيًا. ويتسرّب المتغير الذي يُعيّن داخل دالة إلى نطاق المستدعي ما لم تصرّح به صراحةً باستخدام local. وهذا مصدر شائع للأخطاء الدقيقة في نصوص الصدفة البرمجية.
- يمكن للدوال التي لا تستخدم
localأن تستبدل متغيرات المستدعي بصمت. - تُدمّر متغيرات
localعند عودة الدالة. - تجعل حدود النطاق الواضحة الدوال قابلة لإعادة الاستخدام والاختبار بمعزل عن غيرها.
يعلّمك هذا الدرس كتابة دوال مستقلة بذاتها: تستخدم متغيرات local، وتتواصل بالنتائج عبر رموز الخروج وprintf، ولا تعتمد مطلقًا على حالة عالمية ضمنية.
مشكلة تسرّب المتغيرات العامة
إليك مثالًا عمليًا على تسرّب متغير عام. تضبط الدالة set_name متغيرًا يُسمّى result، فتستبدل بصمت متغير result الخاص بالوظيفة المستدعية.
شغّل هذا السكربت ولاحظ الناتج غير المتوقع — إذ يختفي متغير result الخاص بالوظيفة المستدعية بعد استدعاء الدالة.
#!/usr/bin/env bash
set_name() {
result="Alice" # No 'local' — this is GLOBAL
}
result="important data"
echo "Before: $result"
set_name
echo "After: $result" # Prints 'Alice', not 'important data'تعريف المتغيرات المحلية باستخدام 'local'
تقيّد التعليمة المضمّنة local نطاق المتغير بالدالة المحيطة وأي دوال تستدعيها. خارج الدالة، يكون المتغير إما غير مضبوط أو يحتفظ بقيمته السابقة.
local varname— يعرّف المتغير دون إسناد قيمة إليه.local varname="value"— يعرّف المتغير ويسند إليه قيمة في خطوة واحدة.local -i count=0— يعرّف متغيرًا محليًا من نوع عدد صحيح.local -r PI=3.14159— يعرّف ثابتًا محليًا للقراءة فقط.
أفضل ممارسة: عرّف كل متغير داخل الدالة باستخدام local، ما لم تكن تحتاج إليه عمدًا ليكون عامًا.
#!/usr/bin/env bash
greet() {
local name="$1" # local — safe
local greeting="Hello, ${name}!"
echo "$greeting"
} # 'name' and 'greeting' vanish here
name="global value"
greet "Bob"
echo "name is still: $name" # Prints 'global value'رموز الخروج كقيم إرجاع
لا تستطيع دوال Bash إرجاع سلاسل نصية باستخدام return — إذ تضبط return فقط حالة خروج صحيحة (من 0 إلى 255). ووفقًا للعرف:
return 0— نجاحreturn 1(أو أي قيمة غير صفرية) — فشل
تقرأ الوظيفة المستدعية حالة الخروج عبر $? مباشرة بعد الاستدعاء، أو تستخدم الدالة مباشرةً في شرط if. وتُعد رموز الخروج الطريقة الاصطلاحية للإشارة إلى النجاح أو الفشل من دالة.
#!/usr/bin/env bash
is_even() {
local -i n="$1"
(( n % 2 == 0 )) # arithmetic command: exits 0 if true, 1 if false
}
for num in 2 3 7 10; do
if is_even "$num"; then
echo "$num is even"
else
echo "$num is odd"
fi
doneتمرير نتائج السلاسل النصية عبر printf
عندما تحتاج الدالة إلى إرجاع نتيجة نصية، فإن النمط القياسي هو الطباعة إلى stdout والتقاط الناتج باستخدام استبدال الأوامر $(). ويُفضّل استخدام printf بدلًا من echo للأسباب التالية:
- لا يضيف
printfسطرًا جديدًا في النهاية افتراضيًا (إلا إذا أدرجت\n). - سلوك
printfمتسق ومحدد وفق POSIX، بينما يختلفechoبين الصدَفات. - يزيل استبدال الأوامر الأسطر الجديدة اللاحقة، لذا فإن
printf '%s' "$value"دقيق.
#!/usr/bin/env bash
to_uppercase() {
local input="$1"
printf '%s' "${input^^}" # Bash 4+ parameter expansion
}
word="hello"
upper=$(to_uppercase "$word")
echo "Original: $word"
echo "Upper: $upper"الجمع بين رموز الخروج والناتج في stdout
يمكن للدالة المصممة جيدًا أن تطبع نتيجة (عند النجاح) وأن تشير إلى الفشل (عبر رمز الخروج) في الوقت نفسه. وتحدد الوظيفة المستدعية الإجراء الذي ستتخذه بناءً على رمز الخروج قبل الوثوق بالناتج.
يُستخدم النمط التالي على نطاق واسع في مكتبات Bash الحقيقية:
- عند النجاح: اطبع النتيجة باستخدام
printfونفّذreturn 0. - عند الفشل: اكتب رسالة تشخيصية إلى stderr (وليس stdout) ونفّذ
return 1. - يُبقي إرسال الأخطاء إلى stderr مخرج stdout نظيفًا لتمريره عبر الأنابيب.
#!/usr/bin/env bash
divide() {
local -i numerator="$1"
local -i denominator="$2"
if (( denominator == 0 )); then
printf 'Error: division by zero\n' >&2
return 1
fi
printf '%d' $(( numerator / denominator ))
return 0
}
if result=$(divide 20 4); then
echo "20 / 4 = $result"
else
echo "Division failed."
fi
if result=$(divide 10 0); then
echo "10 / 0 = $result"
else
echo "Division failed (caught the error)."
fiاستخدام 'local' لحماية الدوال التكرارية
يُعد الاستدعاء التكراري من أوضح الأمثلة على أهمية local. يحصل كل استدعاء تكراري على نسخة مستقلة خاصة به من كل متغير local في مكدس الاستدعاءات. ومن دون local، سيستبدل كل استدعاء المتغير العام نفسه، ما يؤدي إلى نتائج خاطئة.
تُعد دالة المضروب أدناه آمنة لأن n وsub محليان لكل إطار في المكدس.
#!/usr/bin/env bash
factorial() {
local -i n="$1"
local -i sub
if (( n <= 1 )); then
printf '1'
return 0
fi
sub=$(factorial $(( n - 1 )))
printf '%d' $(( n * sub ))
}
for i in 1 2 3 4 5 6; do
echo "${i}! = $(factorial $i)"
doneتجنّب فخ الصدفة الفرعية باستخدام local -n (Nameref)
يُنفّذ استبدال الأوامر $() داخل صدفة فرعية. وتكون أي إسنادات للمتغيرات داخله غير مرئية للصدفة الأصلية. عندما تحتاج إلى أن تكتب دالة في متغير يقدمه المستدعي دون استخدام صدفة فرعية، فاستخدم مرجعًا اسميًا (local -n)، وهو متاح في Bash 4.3 والإصدارات الأحدث.
- يجعل
local -n ref="$1"منrefاسمًا مستعارًا للمتغير الذي تُخزّن قيمته الاسمية في$1. - يؤدي إسناد قيمة إلى
refداخل الدالة إلى تغيير متغير المستدعي مباشرةً. - يؤدي ذلك إلى تجنّب الصدفة الفرعية مع إبقاء تفاصيل التنفيذ محلية.
#!/usr/bin/env bash
# Fills caller's array by reference — no subshell needed
read_csv_line() {
local -n _out="$1" # nameref to caller's variable
local line="$2"
local IFS=','
read -ra _out <<< "$line"
}
declare -a fields
read_csv_line fields "alice,30,engineer"
echo "Name: ${fields[0]}"
echo "Age: ${fields[1]}"
echo "Role: ${fields[2]}"إنشاء مكتبة دوال صغيرة
تقسّم مشاريع Bash الواقعية الدوال القابلة لإعادة الاستخدام إلى ملفات مكتبة تستوردها السكربتات باستخدام source (أو عامل النقطة .). قواعد تصميم المكتبات الجيدة:
- يجب أن يكون كل متغير داخل دالة مكتبة
local. - لا تنفّذ دوال المكتبة
exitمطلقًا — بل تنفّذreturnحتى يظل المستدعي قيد التشغيل. - استخدم بادئة مساحة أسماء متسقة (مثل
str_وlog_) لتجنّب تعارض الأسماء. - امنع الاستيراد المزدوج باستخدام متغير حارس.
فيما يلي مكتبة أدوات سلاسل نصية بسيطة تتبع هذه الاصطلاحات.
#!/usr/bin/env bash
# lib/str.sh — string utility library
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
str_trim() {
local str="$1"
str="${str#"${str%%[![:space:]]*}"}"
str="${str%"${str##*[![:space:]]}"}"
printf '%s' "$str"
}
str_repeat() {
local -i times="$2"
local char="$1"
local -i i
for (( i = 0; i < times; i++ )); do
printf '%s' "$char"
done
}
str_contains() {
local haystack="$1"
local needle="$2"
[[ "$haystack" == *"$needle"* ]]
}
# --- self-test when executed directly ---
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
trimmed=$(str_trim " hello world ")
echo "Trimmed: '${trimmed}'"
str_repeat '-' 20; echo
if str_contains "bash scripting" "script"; then
echo "Contains: yes"
fi
fiالتحقق من الوسائط داخل الدوال
ينبغي للدوال التي تستقبل وسائط أن تتحقق منها مبكرًا وأن تعيد رمز خروج محددًا عند وجود إدخال غير صالح. يُسمّى هذا نمط شرط الحراسة — الفشل بسرعة وبوضوح.
- تحقق من عدد الوسائط باستخدام
$#. - تحقق من الأنواع أو التنسيقات قبل تنفيذ أي عمل.
- اطبع الرسائل التشخيصية إلى stderr فقط، وليس إلى stdout مطلقًا.
- استخدم رموز إرجاع غير صفرية مميزة (مثلًا: 1 = وسائط غير صحيحة، و2 = الملف غير موجود) حتى يتمكن المستدعون من الاستجابة بطرق مختلفة لأنماط الفشل المختلفة.
#!/usr/bin/env bash
file_line_count() {
if (( $# != 1 )); then
printf 'Usage: file_line_count <file>\n' >&2
return 1
fi
local file="$1"
if [[ ! -f "$file" ]]; then
printf 'Error: not a file: %s\n' "$file" >&2
return 2
fi
if [[ ! -r "$file" ]]; then
printf 'Error: cannot read: %s\n' "$file" >&2
return 3
fi
local -i count
count=$(wc -l < "$file")
printf '%d' "$count"
return 0
}
# Test with /etc/hosts (exists on every Linux/macOS system)
if lines=$(file_line_count /etc/hosts); then
echo "/etc/hosts has $lines lines"
else
echo "Failed with exit code: $?"
fiجمع كل شيء معًا: مثال واقعي
إليك سكربت كامل مستقل بذاته يوضّح عمل جميع مفاهيم هذا الدرس معًا:
- متغيرات
localفي كل دالة. - رموز الخروج للإشارة إلى النجاح أو الفشل.
- استخدام
printfلتمرير النتائج النصية. - كتابة الأخطاء إلى stderr والنتائج إلى stdout.
- استخدام شروط الحراسة للتحقق من الوسائط.
ادرس مسار التنفيذ: تستخرج parse_version البيانات، وتقارنها version_ge، ثم تستخدم main كلتيهما بطريقة سليمة.
#!/usr/bin/env bash
# Parse a semver string into components via nameref
parse_version() {
local -n _major="$2" _minor="$3" _patch="$4"
local version="$1"
local IFS='.'
local -a parts
read -ra parts <<< "$version"
_major="${parts[0]:-0}"
_minor="${parts[1]:-0}"
_patch="${parts[2]:-0}"
}
# Return 0 if version $1 >= version $2
version_ge() {
local -i maj_a min_a pat_a
local -i maj_b min_b pat_b
parse_version "$1" maj_a min_a pat_a
parse_version "$2" maj_b min_b pat_b
if (( maj_a != maj_b )); then (( maj_a > maj_b ))
elif (( min_a != min_b )); then (( min_a > min_b ))
else (( pat_a >= pat_b ))
fi
}
require_bash_version() {
local required="$1"
local actual="${BASH_VERSION%%(*}"
if version_ge "$actual" "$required"; then
printf 'Bash %s satisfies >= %s\n' "$actual" "$required"
return 0
else
printf 'Error: need Bash >= %s, got %s\n' "$required" "$actual" >&2
return 1
fi
}
main() {
require_bash_version "4.3" || return 1
require_bash_version "99.0" || true # demonstrates failure path
}
mainاختبار المعرفة: المتغيرات المحلية وقيم الإرجاع
تأمل دالة Bash التالية. ما الطريقة الصحيحة لالتقاط نتيجتها النصية في الوظيفة المستدعية، وما العبارة الصحيحة بشأن المتغير tmp؟
transform() {
local tmp="${1,,}" # lowercase
printf '%s' "$tmp"
return 0
}مراجعة: الدوال ذات النطاق المحلي ورموز الإرجاع
تعلّمت في هذا الدرس كيفية كتابة دوال Bash نظيفة وقابلة للتركيب وآمنة:
- استخدم
localدائمًا للمتغيرات داخل الدوال لمنع تلويث نطاق المستدعي. - استخدم رموز الخروج (
return 0/1/N) للإشارة إلى النجاح أو الفشل — فهي تتكامل طبيعيًا معifو&&و||. - استخدم
printfإلى stdout لتمرير النتائج النصية، والتقطها باستخدام$()في الوظيفة المستدعية. - اكتب الأخطاء إلى stderr (
>&2) حتى يظل stdout نظيفًا لتدفق البيانات وتمريرها عبر الأنابيب. - استخدم
local -n(nameref) عندما تحتاج إلى الكتابة في متغير يقدمه المستدعي دون كلفة الصدفة الفرعية. - تجعل شروط الحراسة (التحقق من الوسائط مبكرًا والإرجاع فورًا عند الإدخال غير الصالح) الدوال متينة وموضحة لذاتها.
- ينبغي استيراد ملفات المكتبة، واستخدام بادئات مساحات الأسماء، وعدم استدعاء
exitمطلقًا، ومنع الاستيراد المزدوج.
إن إتقان هذه الأنماط هو ما يميّز بين السكربتات الهشة المعدّة لمرة واحدة وقواعد شيفرة Bash الاحترافية القابلة للصيانة.
الأسئلة الشائعة
هل درس «تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع» مجاني؟
نعم — نص درس «تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Linux Command Line & Bash Scripting Mastery، انتقل إلى CoddyKit PRO. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.
ماذا ستتعلم في «تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع»؟
اكتب دوالًا تستخدم متغيرات محلية وحالات خروج وقيم إرجاع مبنية على printf بدلًا من المتغيرات العامة الهشة. تتمرن على Linux Command Line & Bash Scripting Mastery مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Linux Command Line & Bash Scripting Mastery؟
لا تُشترط خبرة سابقة. Linux Command Line & Bash Scripting Mastery على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Linux Command Line & Bash Scripting Mastery هذا؟
نعم. كل درس في Linux Command Line & Bash Scripting Mastery يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع
- بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها
- تحليل الأعلام والوسائط باستخدام getopts
- تمرير المصفوفات والخرائط الترابطية بين الدوال