تمرير المصفوفات والخرائط الترابطية بين الدوال
استخدم namerefs والتوسعة غير المباشرة لتمرير هياكل البيانات المعقدة إلى الدوال ومنها بأمان.
تمرير المصفوفات والخرائط الترابطية بين الدوال درس مجاني في DevOps Bootcamp على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DevOps Bootcamp، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DevOps Bootcamp 4 دروس في المجموع.
لماذا يُعد تمرير المصفوفات إلى الدوال أمرًا معقدًا
في Bash، لا تُعد المصفوفات والمصفوفات الترابطية قيمًا من الدرجة الأولى. عند محاولة تمرير مصفوفة إلى دالة، لا يمكنك استخدام $my_array ببساطة، إذ يؤدي ذلك إلى توسيع العنصر الأول فقط.
تأمل هذه المحاولة غير الصحيحة:
my_func "${my_array[@]}"تستقبل الدالة قائمة مسطحة من الكلمات. ولا تملك أي وسيلة لمعرفة أين ينتهي أحد العناصر ويبدأ العنصر التالي، كما تفقد بنية المصفوفة بالكامل.
يوفر Bash آليتين موثوقتين لتمرير هياكل البيانات المعقدة:
- Namerefs (
declare -n) — مرجع إلى متغير آخر باستخدام اسمه (في Bash 4.3 والإصدارات الأحدث) - Indirect expansion (
${!varname[@]}) — الوصول إلى متغير يُخزَّن اسمه في متغير آخر (في إصدارات Bash الأقدم)
يغطي هذا الدرس كلتا الطريقتين، لتتمكنوا من كتابة دوال قوية قابلة لإعادة الاستخدام تعمل مع المصفوفات والخرائط.
تمرير مصفوفة باستخدام اسمها عبر Nameref
أنظف نهج حديث هو استخدام nameref: تمررون اسم المصفوفة كسلسلة نصية، ثم تعرّفون داخل الدالة متغيرًا محليًا يمثل مرجعًا إليها.
الصياغة الأساسية:
declare -n ref="$1"— يصبحrefاسمًا مستعارًا لأي اسم متغير تم تمريره كوسيط أول- يمكنكم قراءة
refوالتكرار عبر عناصره وتعديله تمامًا كما لو كان المصفوفة الأصلية
يؤدي ذلك إلى تجنب نسخ المصفوفة بأكملها، ويتيح للدالة التعامل مع مصفوفات بأي حجم.
#!/usr/bin/env bash
print_array() {
declare -n arr="$1" # nameref: arr -> caller's variable
echo "Array has ${#arr[@]} elements:"
for elem in "${arr[@]}"; do
echo " - $elem"
done
}
fruits=(apple banana cherry)
print_array fruitsتعديل مصفوفة المستدعي داخل دالة
بما أن nameref هو اسم مستعار حقيقي، فإن الكتابة من خلال nameref تغيّر المتغير الأصلي للمستدعي. ولا حاجة إلى إعادة القيم باستخدام echo أو إلى استخدام المتغيرات العامة.
يجعل ذلك namerefs مثالية للدوال التي تحوّل المصفوفات في موضعها، مثل توحيد تنسيق السلاسل النصية أو تصفية العناصر أو فرزها.
#!/usr/bin/env bash
to_uppercase() {
declare -n _arr="$1"
for i in "${!_arr[@]}"; do
_arr[$i]="${_arr[$i]^^}" # ^^ = uppercase in Bash 4+
done
}
words=(hello world bash)
echo "Before: ${words[@]}"
to_uppercase words
echo "After: ${words[@]}"إرجاع مصفوفة جديدة عبر Nameref
يمكن للدالة أيضًا الكتابة في مصفوفة يمررها المستدعي، وذلك بقبول اسمها كمعامل إخراج. ينشئ المستدعي مصفوفة فارغة، ويمرر اسمها، ثم تملؤها الدالة.
يحاكي هذا النمط الأسلوب التقليدي في C المتمثل في تمرير مؤشر إلى مخزن نتائج، لكن تنفيذه هنا يتم بالكامل داخل الصدفة.
#!/usr/bin/env bash
get_even_numbers() {
local -a source=("$@")
# Last argument is the output array name — pop it off
local out_name="${source[-1]}"
unset 'source[-1]'
declare -n _out="$out_name"
_out=() # clear
for n in "${source[@]}"; do
(( n % 2 == 0 )) && _out+=("$n")
done
}
declare -a evens
get_even_numbers 1 2 3 4 5 6 7 8 evens
echo "Evens: ${evens[@]}"فخ تعارض أسماء Nameref
هناك مشكلة خطيرة واحدة في namerefs: إذا شارك متغير nameref المحلي الاسم نفسه مع متغير المستدعي، يدخل Bash في حلقة إحالة ذاتية لا نهائية ويفشل البرنامج النصي.
على سبيل المثال، إذا سمّى المستدعي مصفوفتَه arr ونفذت الدالة declare -n arr="$1"، فإن nameref المسمى arr سيشير إلى نفسه.
القاعدة المتبعة لتجنب ذلك: أضيفوا شرطة سفلية في بداية أسماء متغيرات nameref الداخلية، مثل _arr و_ref و_out. فنادرًا ما يستخدم المستدعون هذه الأسماء، مما يجعل التعارضات نادرة للغاية.
#!/usr/bin/env bash
# DANGER: caller uses variable named 'arr'
arr=(one two three)
bad_func() {
declare -n arr="$1" # collision! arr refers to itself
echo "${arr[@]}"
}
good_func() {
declare -n _arr="$1" # underscore prefix avoids collision
echo "${_arr[@]}"
}
# bad_func arr # would hang / error
good_func arrتمرير المصفوفات الترابطية (الخرائط) باستخدام الاسم
تعمل المصفوفات الترابطية (declare -A) بالطريقة نفسها مع namerefs. مرروا اسم الخريطة كسلسلة نصية، وعرّفوا nameref داخل الدالة، لتحصلوا على صلاحية كاملة لقراءة جميع أزواج المفتاح والقيمة وتعديلها.
يُعد ذلك بالغ الأهمية لخرائط الإعدادات ومحللات الخيارات وجداول البحث التي يجب مشاركتها بين دوال المكتبة.
#!/usr/bin/env bash
print_map() {
declare -n _map="$1"
echo "Map contents:"
for key in "${!_map[@]}"; do
printf ' %-15s => %s\n' "$key" "${_map[$key]}"
done
}
declare -A config
config[host]="localhost"
config[port]="5432"
config[db]="myapp"
print_map configدمج خريطتين باستخدام Nameref
بما أن namerefs تمنح الدالة اسمًا مستعارًا حيًا، يمكنكم تنفيذ أدوات مثل دمج الخرائط بطريقة واضحة. مرروا خريطة الوجهة وخريطة مصدر واحدة أو أكثر باستخدام أسمائها؛ ثم تكرّر الدالة عبر مفاتيح المصدر وتكتبها في الوجهة.
تتيح namerefs كتابة دالة عامة واحدة باسم merge_map تعمل مع أي مصفوفات ترابطية في البرنامج النصي.
#!/usr/bin/env bash
merge_map() {
# merge_map dest src
declare -n _dest="$1"
declare -n _src="$2"
for key in "${!_src[@]}"; do
_dest["$key"]="${_src[$key]}"
done
}
declare -A defaults=([timeout]=30 [retries]=3 [verbose]=false)
declare -A overrides=([retries]=5 [log_level]=debug)
merge_map defaults overrides
for k in "${!defaults[@]}"; do
echo "$k = ${defaults[$k]}"
doneالتوسيع غير المباشر: التوافق مع إصدارات Bash الأقدم
إذا كان يجب دعم Bash 4.0–4.2 (أو الصدفات الافتراضية الأقدم في macOS)، فلن يكون declare -n متاحًا. والبديل هو التوسيع غير المباشر باستخدام ${!varname}.
يقيّم التوسيع غير المباشر قيمة varname باعتبارها اسم متغير، ثم يعيد قيمة ذلك المتغير. وبالنسبة إلى المصفوفات، تدمجه مع صياغة خاصة للحصول على جميع العناصر.
القيود مقارنةً بـ namerefs:
- للقراءة فقط عمليًا (إذ تتطلب الكتابة مجددًا استخدام
eval، الذي ينطوي على مخاطر الحقن) - لا يمكن تكرار مفاتيح المصفوفات الترابطية بأمان من دون
eval - استخدموا namerefs متى أمكن، والجؤوا إلى التوسيع غير المباشر عند الضرورة فقط
#!/usr/bin/env bash
sum_array() {
local arr_name="$1"
# Indirect: build a temporary copy by expanding arr_name[@]
local -a items
eval "items=( \"\${${arr_name}[@]}\" )"
local total=0
for n in "${items[@]}"; do
(( total += n ))
done
echo "$total"
}
numbers=(10 20 30 40)
result=$(sum_array numbers)
echo "Sum: $result"إنشاء مكتبة نصوص برمجية معيارية
في مشاريع Bash الاحترافية، تُخزَّن دوال الأدوات في ملفات مكتبة (مثل lib/array_utils.sh)، وتستوردها البرامج النصية التي تحتاج إليها.
النمط المتبع هو:
- يعرّف كل ملف مكتبة الدوال فقط، من دون تأثيرات جانبية على المستوى الأعلى
- الحماية من استيراد الملف مرتين باستخدام متغير حارس:
[[ -n $__LIB_LOADED ]] && return; __LIB_LOADED=1 - تستخدم البرامج النصية المستهلكة
sourceأو.لتحميل المكتبة - تتبادل الدوال البيانات المعقدة عبر namerefs، مع إبقاء المنطق بأكمله داخل الدوال
ينتج عن ذلك نصوص برمجية قابلة للاختبار وإعادة الاستخدام والصيانة، وليست حلولًا مرتجلة لمرة واحدة في الصدفة.
مثال عملي: محلل خيارات يعيد خريطة
حالة استخدام واقعية: محلل لخيارات سطر الأوامر يملأ مصفوفة ترابطية يمررها المستدعي. يمرر المستدعي اسم المصفوفة كوسيط أول، تليه وسائط "$@" الفعلية.
يتيح هذا النمط لأي برنامج نصي إعادة استخدام المحلل نفسه من دون نسخ الشيفرة أو الاعتماد على المتغيرات العامة.
#!/usr/bin/env bash
parse_opts() {
declare -n _opts="$1"
shift
while [[ $# -gt 0 ]]; do
case "$1" in
--host) _opts[host]="$2"; shift 2 ;;
--port) _opts[port]="$2"; shift 2 ;;
--user) _opts[user]="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; shift ;;
esac
done
}
declare -A opts=([host]=localhost [port]=5432 [user]=admin)
parse_opts opts --host db.example.com --port 3306
echo "Connecting to ${opts[host]}:${opts[port]} as ${opts[user]}"دمج Namerefs للإدخال والإخراج في دالة واحدة
يمكن للدوال الاحتفاظ بعدة namerefs في الوقت نفسه، واحد للإدخال وآخر للإخراج. يتيح ذلك إنشاء دوال تحويل خالصة: تقرأ من بنية مصدر، وتحسب نتيجة، وتكتبها في بنية وجهة، وكل ذلك من دون المساس بالحالة العامة.
يؤدي استخدام أسماء مختلفة تبدأ بشرطة سفلية لكل nameref إلى استحالة التعارضات، حتى عندما تكون أسماء متغيرات المستدعي بسيطة.
#!/usr/bin/env bash
filter_map_by_key_prefix() {
# Usage: filter_map_by_key_prefix src_map dest_map prefix
declare -n _src="$1"
declare -n _dst="$2"
local prefix="$3"
_dst=()
for key in "${!_src[@]}"; do
if [[ $key == ${prefix}* ]]; then
_dst["$key"]="${_src[$key]}"
fi
done
}
declare -A settings=(
[db_host]=localhost [db_port]=5432
[cache_ttl]=300 [cache_driver]=redis
[app_debug]=true
)
declare -A db_settings
filter_map_by_key_prefix settings db_settings db_
for k in "${!db_settings[@]}"; do
echo "$k = ${db_settings[$k]}"
doneاختبار المعرفة: Namerefs وتمرير المصفوفات
اختبروا فهمكم لتمرير المصفوفات والخرائط بين الدوال في Bash.
مراجعة: تمرير هياكل البيانات المعقدة في Bash
إليكم ما تعلمتموه في هذا الدرس:
- لا يمكن تمرير المصفوفات بالقيمة، لذا مرروا اسم المتغير كسلسلة نصية بدلًا من ذلك.
declare -n ref="$varname"ينشئ nameref، وهو اسم مستعار حي لمتغير المستدعي. تمر عمليات القراءة والكتابة معًا إلى المتغير الأصلي. ويتطلب Bash 4.3 أو إصدارًا أحدث.- يحدث تعارض nameref عندما يتشارك المتغير الداخلي والخارجي الاسم نفسه. امنعوا ذلك بإضافة بادئة إلى namerefs الداخلية، مثل
_arrو_map. - تعمل معاملات الإخراج عبر تمرير اسم مصفوفة أو خريطة نتائج فارغة؛ ثم تملؤها الدالة من خلال nameref.
- تتيح namerefs المتعددة في دالة واحدة إجراء تحويلات خالصة من الإدخال إلى الإخراج من دون تأثيرات جانبية عامة.
- يُعد التوسيع غير المباشر (
${!varname[@]}+eval) بديلًا أقدم للإصدارات الأقدم من Bash 4.3، لكنه ينطوي على مخاطر الحقن، ويكون للقراءة فقط عمليًا. - اجمعوا هذه الأساليب مع ملفات المكتبة المستوردة لإنشاء قواعد شيفرة Bash معيارية وقابلة لإعادة الاستخدام والاختبار.
الأسئلة الشائعة
هل درس «تمرير المصفوفات والخرائط الترابطية بين الدوال» مجاني؟
نعم — نص درس «تمرير المصفوفات والخرائط الترابطية بين الدوال» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DevOps Bootcamp، انتقل إلى CoddyKit PRO. تتضمن دورة DevOps Bootcamp 4 دروس في المجموع.
ماذا ستتعلم في «تمرير المصفوفات والخرائط الترابطية بين الدوال»؟
استخدم namerefs والتوسعة غير المباشرة لتمرير هياكل البيانات المعقدة إلى الدوال ومنها بأمان. تتمرن على DevOps Bootcamp مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DevOps Bootcamp؟
لا تُشترط خبرة سابقة. DevOps Bootcamp على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «تمرير المصفوفات والخرائط الترابطية بين الدوال»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DevOps Bootcamp هذا؟
نعم. كل درس في DevOps Bootcamp يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تصميم الدوال باستخدام النطاق المحلي ورموز الإرجاع
- بناء مكتبات Bash قابلة لإعادة الاستخدام وتحميلها
- تحليل الأعلام والوسائط باستخدام getopts
- تمرير المصفوفات والخرائط الترابطية بين الدوال